1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00

fix: doc and tests

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2025-11-11 16:36:12 +01:00
parent 4f8a557072
commit 13063bbd88
28 changed files with 2619 additions and 4 deletions

View File

@@ -87,6 +87,6 @@ func Example_sort() {
// [abc klm zyx]
// [zyx klm abc]
// [None[int] Some[int](42) Some[int](1337)]
// [{c {false 0}} {b {true 10}} {d {true 10}} {a {true 30}}]
// [{c {0 false}} {b {10 true}} {d {10 true}} {a {30 true}}]
}

View File

@@ -76,8 +76,8 @@ func TestAp(t *testing.T) {
f := S.Size
assert.Equal(t, Right[string](3), F.Pipe1(Right[string](f), Ap[int](Right[string]("abc"))))
assert.Equal(t, Left[int]("maError"), F.Pipe1(Right[string](f), Ap[int](Left[string, string]("maError"))))
assert.Equal(t, Left[int]("mabError"), F.Pipe1(Left[func(string) int]("mabError"), Ap[int](Left[string, string]("maError"))))
assert.Equal(t, Left[int]("maError"), F.Pipe1(Right[string](f), Ap[int](Left[string]("maError"))))
assert.Equal(t, Left[int]("mabError"), F.Pipe1(Left[func(string) int]("mabError"), Ap[int](Left[string]("maError"))))
}
func TestAlt(t *testing.T) {

View File

@@ -0,0 +1,87 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fromoption
import (
F "github.com/IBM/fp-go/v2/function"
O "github.com/IBM/fp-go/v2/option"
)
func FromPredicate[A, HKTEA any](fromOption func(Option[A]) HKTEA, pred func(A) bool) func(A) HKTEA {
return F.Flow2(O.FromPredicate(pred), fromOption)
}
// func MonadFromOption[E, A, HKTEA any](
// fromEither func(ET.Either[E, A]) HKTEA,
// onNone func() E,
// ma O.Option[A],
// ) HKTEA {
// return F.Pipe1(
// O.MonadFold(
// ma,
// F.Nullary2(onNone, ET.Left[A, E]),
// ET.Right[E, A],
// ),
// fromEither,
// )
// }
// func FromOptionK[A, E, B, HKTEB any](
// fromEither func(ET.Either[E, B]) HKTEB,
// onNone func() E) func(f func(A) O.Option[B]) func(A) HKTEB {
// // helper
// return F.Bind2nd(F.Flow2[func(A) O.Option[B], func(O.Option[B]) HKTEB, A, O.Option[B], HKTEB], FromOption(fromEither, onNone))
// }
func MonadChainOptionK[A, B, HKTEA, HKTEB any](
mchain func(HKTEA, func(A) HKTEB) HKTEB,
fromOption func(Option[B]) HKTEB,
ma HKTEA,
f func(A) Option[B]) HKTEB {
return mchain(ma, F.Flow2(f, fromOption))
}
func ChainOptionK[A, B, HKTEA, HKTEB any](
mchain func(func(A) HKTEB) func(HKTEA) HKTEB,
fromOption func(Option[B]) HKTEB,
f func(A) Option[B]) func(HKTEA) HKTEB {
return mchain(F.Flow2(f, fromOption))
}
// func ChainOptionK[A, E, B, HKTEA, HKTEB any](
// mchain func(HKTEA, func(A) HKTEB) HKTEB,
// fromEither func(ET.Either[E, B]) HKTEB,
// onNone func() E,
// ) func(f func(A) O.Option[B]) func(ma HKTEA) HKTEB {
// return F.Flow2(FromOptionK[A](fromEither, onNone), F.Bind1st(F.Bind2nd[HKTEA, func(A) HKTEB, HKTEB], mchain))
// }
// func MonadChainFirstEitherK[A, E, B, HKTEA, HKTEB any](
// mchain func(HKTEA, func(A) HKTEA) HKTEA,
// mmap func(HKTEB, func(B) A) HKTEA,
// fromEither func(ET.Either[E, B]) HKTEB,
// ma HKTEA,
// f func(A) ET.Either[E, B]) HKTEA {
// return C.MonadChainFirst(mchain, mmap, ma, F.Flow2(f, fromEither))
// }
// func ChainFirstEitherK[A, E, B, HKTEA, HKTEB any](
// mchain func(func(A) HKTEA) func(HKTEA) HKTEA,
// mmap func(func(B) A) func(HKTEB) HKTEA,
// fromEither func(ET.Either[E, B]) HKTEB,
// f func(A) ET.Either[E, B]) func(HKTEA) HKTEA {
// return C.ChainFirst(mchain, mmap, F.Flow2(f, fromEither))
// }

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2024 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fromoption
import (
"github.com/IBM/fp-go/v2/option"
)
type (
Option[T any] = option.Option[T]
FromOption[A, HKTA any] interface {
FromEither(Option[A]) HKTA
}
)

View File

@@ -82,6 +82,11 @@ func MatchE[A, HKTEA, HKTB any](mchain func(HKTEA, func(O.Option[A]) HKTB) HKTB,
return F.Bind2nd(mchain, O.Fold(onNone, onSome))
}
//go:inline
func GetOrElse[A, HKTEA, HKTB any](mchain func(HKTEA, func(O.Option[A]) HKTB) HKTB, onNone func() HKTB, onSome func(A) HKTB) func(HKTEA) HKTB {
return MatchE(mchain, onNone, onSome)
}
func FromOptionK[A, B, HKTB any](
fof func(O.Option[B]) HKTB,
f func(A) O.Option[B]) func(A) HKTB {
@@ -123,3 +128,7 @@ func Alt[LAZY ~func() HKTFA, A, HKTFA any](
return fchain(O.Fold(second, F.Flow2(O.Of[A], fof)))
}
func SomeF[A, HKTA, HKTEA any](fmap func(HKTA, func(A) O.Option[A]) HKTEA, fa HKTA) HKTEA {
return fmap(fa, O.Some[A])
}

View File

@@ -39,8 +39,8 @@ var (
// var opt Option[int] = Some(42) // Contains a value
// var opt Option[int] = None[int]() // Contains no value
type Option[A any] struct {
isSome bool
value A
isSome bool
}
type (

View File

@@ -43,6 +43,11 @@ func FromPredicate[A any](pred func(A) bool) Kleisli[A, A] {
return F.Bind2nd(fromPredicate[A], pred)
}
func FromZero[A comparable]() Kleisli[A, A] {
var zero A
return FromPredicate(func(a A) bool { return zero == a })
}
// FromNillable converts a pointer to an Option.
// Returns Some if the pointer is non-nil, None otherwise.
//

88
v2/readeroption/array.go Normal file
View File

@@ -0,0 +1,88 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
G "github.com/IBM/fp-go/v2/readeroption/generic"
)
// TraverseArray transforms an array by applying a function that returns a ReaderOption to each element.
// If any element results in None, the entire result is None.
// Otherwise, returns Some containing an array of all the unwrapped values.
//
// This is useful for performing a sequence of operations that may fail on each element of an array,
// where you want all operations to succeed or the entire computation to fail.
//
// Example:
//
// type DB struct { ... }
//
// findUser := func(id int) readeroption.ReaderOption[DB, User] { ... }
//
// userIDs := []int{1, 2, 3}
// result := F.Pipe1(
// readeroption.Of[DB](userIDs),
// readeroption.Chain(readeroption.TraverseArray[DB](findUser)),
// )
// // result will be Some([]User) if all users are found, None otherwise
func TraverseArray[E, A, B any](f func(A) ReaderOption[E, B]) Kleisli[E, []A, []B] {
return G.TraverseArray[ReaderOption[E, B], ReaderOption[E, []B], []A](f)
}
// TraverseArrayWithIndex is like TraverseArray but the function also receives the index of each element.
//
// Example:
//
// type DB struct { ... }
//
// processWithIndex := func(idx int, value string) readeroption.ReaderOption[DB, Result] {
// // Use idx in processing
// return readeroption.Asks(func(db DB) option.Option[Result] { ... })
// }
//
// values := []string{"a", "b", "c"}
// result := readeroption.TraverseArrayWithIndex[DB](processWithIndex)(values)
func TraverseArrayWithIndex[E, A, B any](f func(int, A) ReaderOption[E, B]) func([]A) ReaderOption[E, []B] {
return G.TraverseArrayWithIndex[ReaderOption[E, B], ReaderOption[E, []B], []A](f)
}
// SequenceArray converts an array of ReaderOption values into a ReaderOption of an array.
// If any element is None, the entire result is None.
// Otherwise, returns Some containing an array of all the unwrapped values.
//
// This is useful when you have multiple independent ReaderOption computations and want to
// combine their results into a single array.
//
// Example:
//
// type Config struct { ... }
//
// user1 := readeroption.Of[Config](User{ID: 1, Name: "Alice"})
// user2 := readeroption.Of[Config](User{ID: 2, Name: "Bob"})
// user3 := readeroption.None[Config, User]()
//
// result := readeroption.SequenceArray([]readeroption.ReaderOption[Config, User]{
// user1, user2, user3,
// })
// // result(config) will be option.None[[]User]() because user3 is None
//
// result2 := readeroption.SequenceArray([]readeroption.ReaderOption[Config, User]{
// user1, user2,
// })
// // result2(config) will be option.Some([]User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}})
func SequenceArray[E, A any](ma []ReaderOption[E, A]) ReaderOption[E, []A] {
return G.SequenceArray[ReaderOption[E, A], ReaderOption[E, []A]](ma)
}

View File

@@ -0,0 +1,107 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
"context"
"testing"
A "github.com/IBM/fp-go/v2/array"
F "github.com/IBM/fp-go/v2/function"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
func TestSequenceArray(t *testing.T) {
n := 10
readers := A.MakeBy(n, Of[context.Context, int])
exp := O.Of(A.MakeBy(n, F.Identity[int]))
g := F.Pipe1(
readers,
SequenceArray[context.Context, int],
)
assert.Equal(t, exp, g(context.Background()))
}
func TestTraverseArray(t *testing.T) {
// Function that doubles a number if it's positive
doubleIfPositive := func(x int) ReaderOption[context.Context, int] {
if x > 0 {
return Of[context.Context](x * 2)
}
return None[context.Context, int]()
}
// Test with all positive numbers
input1 := []int{1, 2, 3}
g1 := F.Pipe1(
Of[context.Context](input1),
Chain(TraverseArray[context.Context](doubleIfPositive)),
)
assert.Equal(t, O.Of([]int{2, 4, 6}), g1(context.Background()))
// Test with a negative number (should return None)
input2 := []int{1, -2, 3}
g2 := F.Pipe1(
Of[context.Context](input2),
Chain(TraverseArray[context.Context](doubleIfPositive)),
)
assert.Equal(t, O.None[[]int](), g2(context.Background()))
// Test with empty array
input3 := []int{}
g3 := F.Pipe1(
Of[context.Context](input3),
Chain(TraverseArray[context.Context](doubleIfPositive)),
)
assert.Equal(t, O.Of([]int{}), g3(context.Background()))
}
func TestTraverseArrayWithIndex(t *testing.T) {
// Function that multiplies value by its index if index is even
multiplyByIndexIfEven := func(idx int, x int) ReaderOption[context.Context, int] {
if idx%2 == 0 {
return Of[context.Context](x * idx)
}
return Of[context.Context](x)
}
input := []int{10, 20, 30, 40}
g := TraverseArrayWithIndex[context.Context](multiplyByIndexIfEven)(input)
// Expected: [10*0, 20, 30*2, 40] = [0, 20, 60, 40]
assert.Equal(t, O.Of([]int{0, 20, 60, 40}), g(context.Background()))
}
func TestTraverseArrayWithIndexNone(t *testing.T) {
// Function that returns None for odd indices
noneForOdd := func(idx int, x int) ReaderOption[context.Context, int] {
if idx%2 == 0 {
return Of[context.Context](x)
}
return None[context.Context, int]()
}
input := []int{10, 20, 30}
g := TraverseArrayWithIndex[context.Context](noneForOdd)(input)
// Should return None because index 1 returns None
assert.Equal(t, O.None[[]int](), g(context.Background()))
}

303
v2/readeroption/bind.go Normal file
View File

@@ -0,0 +1,303 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
F "github.com/IBM/fp-go/v2/function"
L "github.com/IBM/fp-go/v2/optics/lens"
G "github.com/IBM/fp-go/v2/readeroption/generic"
)
// Do creates an empty context of type [S] to be used with the [Bind] operation.
// This is the starting point for do-notation style composition.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
// type Env struct {
// UserService UserService
// ConfigService ConfigService
// }
// result := readereither.Do[Env, error](State{})
func Do[R, S any](
empty S,
) ReaderOption[R, S] {
return G.Do[ReaderOption[R, S]](empty)
}
// Bind attaches the result of a computation to a context [S1] to produce a context [S2].
// This enables sequential composition where each step can depend on the results of previous steps
// and access the shared environment.
//
// The setter function takes the result of the computation and returns a function that
// updates the context from S1 to S2.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
// type Env struct {
// UserService UserService
// ConfigService ConfigService
// }
//
// result := F.Pipe2(
// readereither.Do[Env, error](State{}),
// readereither.Bind(
// func(user User) func(State) State {
// return func(s State) State { s.User = user; return s }
// },
// func(s State) readereither.ReaderOption[Env, error, User] {
// return readereither.Asks(func(env Env) either.Either[error, User] {
// return env.UserService.GetUser()
// })
// },
// ),
// readereither.Bind(
// func(cfg Config) func(State) State {
// return func(s State) State { s.Config = cfg; return s }
// },
// func(s State) readereither.ReaderOption[Env, error, Config] {
// // This can access s.User from the previous step
// return readereither.Asks(func(env Env) either.Either[error, Config] {
// return env.ConfigService.GetConfigForUser(s.User.ID)
// })
// },
// ),
// )
func Bind[R, S1, S2, T any](
setter func(T) func(S1) S2,
f Kleisli[R, S1, T],
) func(ReaderOption[R, S1]) ReaderOption[R, S2] {
return G.Bind[ReaderOption[R, S1], ReaderOption[R, S2]](setter, f)
}
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
func Let[R, S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) T,
) func(ReaderOption[R, S1]) ReaderOption[R, S2] {
return G.Let[ReaderOption[R, S1], ReaderOption[R, S2]](setter, f)
}
// LetTo attaches the a value to a context [S1] to produce a context [S2]
func LetTo[R, S1, S2, T any](
setter func(T) func(S1) S2,
b T,
) func(ReaderOption[R, S1]) ReaderOption[R, S2] {
return G.LetTo[ReaderOption[R, S1], ReaderOption[R, S2]](setter, b)
}
// BindTo initializes a new state [S1] from a value [T]
func BindTo[R, S1, T any](
setter func(T) S1,
) func(ReaderOption[R, T]) ReaderOption[R, S1] {
return G.BindTo[ReaderOption[R, S1], ReaderOption[R, T]](setter)
}
// ApS attaches a value to a context [S1] to produce a context [S2] by considering
// the context and the value concurrently (using Applicative rather than Monad).
// This allows independent computations to be combined without one depending on the result of the other.
//
// Unlike Bind, which sequences operations, ApS can be used when operations are independent
// and can conceptually run in parallel.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
// type Env struct {
// UserService UserService
// ConfigService ConfigService
// }
//
// // These operations are independent and can be combined with ApS
// getUser := readereither.Asks(func(env Env) either.Either[error, User] {
// return env.UserService.GetUser()
// })
// getConfig := readereither.Asks(func(env Env) either.Either[error, Config] {
// return env.ConfigService.GetConfig()
// })
//
// result := F.Pipe2(
// readereither.Do[Env, error](State{}),
// readereither.ApS(
// func(user User) func(State) State {
// return func(s State) State { s.User = user; return s }
// },
// getUser,
// ),
// readereither.ApS(
// func(cfg Config) func(State) State {
// return func(s State) State { s.Config = cfg; return s }
// },
// getConfig,
// ),
// )
func ApS[R, S1, S2, T any](
setter func(T) func(S1) S2,
fa ReaderOption[R, T],
) func(ReaderOption[R, S1]) ReaderOption[R, S2] {
return G.ApS[ReaderOption[R, S1], ReaderOption[R, S2]](setter, fa)
}
// ApSL attaches a value to a context using a lens-based setter.
// This is a convenience function that combines ApS with a lens, allowing you to use
// optics to update nested structures in a more composable way.
//
// The lens parameter provides both the getter and setter for a field within the structure S.
// This eliminates the need to manually write setter functions.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
// type Env struct {
// UserService UserService
// ConfigService ConfigService
// }
//
// configLens := lens.MakeLens(
// func(s State) Config { return s.Config },
// func(s State, c Config) State { s.Config = c; return s },
// )
//
// getConfig := readereither.Asks(func(env Env) either.Either[error, Config] {
// return env.ConfigService.GetConfig()
// })
// result := F.Pipe2(
// readereither.Of[Env, error](State{}),
// readereither.ApSL(configLens, getConfig),
// )
func ApSL[R, S, T any](
lens L.Lens[S, T],
fa ReaderOption[R, T],
) func(ReaderOption[R, S]) ReaderOption[R, S] {
return ApS(lens.Set, fa)
}
// BindL is a variant of Bind that uses a lens to focus on a specific part of the context.
// This provides a more ergonomic API when working with nested structures, eliminating
// the need to manually write setter functions.
//
// The lens parameter provides both a getter and setter for a field of type T within
// the context S. The function f receives the current value of the focused field and
// returns a ReaderOption computation that produces an updated value.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
// type Env struct {
// UserService UserService
// ConfigService ConfigService
// }
//
// userLens := lens.MakeLens(
// func(s State) User { return s.User },
// func(s State, u User) State { s.User = u; return s },
// )
//
// result := F.Pipe2(
// readereither.Do[Env, error](State{}),
// readereither.BindL(userLens, func(user User) readereither.ReaderOption[Env, error, User] {
// return readereither.Asks(func(env Env) either.Either[error, User] {
// return env.UserService.GetUser()
// })
// }),
// )
func BindL[R, S, T any](
lens L.Lens[S, T],
f Kleisli[R, T, T],
) func(ReaderOption[R, S]) ReaderOption[R, S] {
return Bind(lens.Set, F.Flow2(lens.Get, f))
}
// LetL is a variant of Let that uses a lens to focus on a specific part of the context.
// This provides a more ergonomic API when working with nested structures, eliminating
// the need to manually write setter functions.
//
// The lens parameter provides both a getter and setter for a field of type T within
// the context S. The function f receives the current value of the focused field and
// returns a new value (without wrapping in a ReaderOption).
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
//
// configLens := lens.MakeLens(
// func(s State) Config { return s.Config },
// func(s State, c Config) State { s.Config = c; return s },
// )
//
// result := F.Pipe2(
// readereither.Do[any, error](State{Config: Config{Host: "localhost"}}),
// readereither.LetL(configLens, func(cfg Config) Config {
// cfg.Port = 8080
// return cfg
// }),
// )
func LetL[R, S, T any](
lens L.Lens[S, T],
f func(T) T,
) func(ReaderOption[R, S]) ReaderOption[R, S] {
return Let[R](lens.Set, F.Flow2(lens.Get, f))
}
// LetToL is a variant of LetTo that uses a lens to focus on a specific part of the context.
// This provides a more ergonomic API when working with nested structures, eliminating
// the need to manually write setter functions.
//
// The lens parameter provides both a getter and setter for a field of type T within
// the context S. The value b is set directly to the focused field.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
//
// configLens := lens.MakeLens(
// func(s State) Config { return s.Config },
// func(s State, c Config) State { s.Config = c; return s },
// )
//
// newConfig := Config{Host: "localhost", Port: 8080}
// result := F.Pipe2(
// readereither.Do[any, error](State{}),
// readereither.LetToL(configLens, newConfig),
// )
func LetToL[R, S, T any](
lens L.Lens[S, T],
b T,
) func(ReaderOption[R, S]) ReaderOption[R, S] {
return LetTo[R](lens.Set, b)
}

View File

@@ -0,0 +1,99 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
"context"
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
func getLastName(s utils.Initial) ReaderOption[context.Context, string] {
return Of[context.Context]("Doe")
}
func getGivenName(s utils.WithLastName) ReaderOption[context.Context, string] {
return Of[context.Context]("John")
}
func TestBind(t *testing.T) {
res := F.Pipe3(
Do[context.Context](utils.Empty),
Bind(utils.SetLastName, getLastName),
Bind(utils.SetGivenName, getGivenName),
Map[context.Context](utils.GetFullName),
)
assert.Equal(t, res(context.Background()), O.Of("John Doe"))
}
func TestApS(t *testing.T) {
res := F.Pipe3(
Do[context.Context](utils.Empty),
ApS(utils.SetLastName, Of[context.Context]("Doe")),
ApS(utils.SetGivenName, Of[context.Context]("John")),
Map[context.Context](utils.GetFullName),
)
assert.Equal(t, res(context.Background()), O.Of("John Doe"))
}
func TestLet(t *testing.T) {
res := F.Pipe3(
Do[context.Context](utils.Empty),
Let[context.Context](utils.SetLastName, func(s utils.Initial) string {
return "Doe"
}),
Let[context.Context](utils.SetGivenName, func(s utils.WithLastName) string {
return "John"
}),
Map[context.Context](utils.GetFullName),
)
assert.Equal(t, res(context.Background()), O.Of("John Doe"))
}
func TestLetTo(t *testing.T) {
res := F.Pipe3(
Do[context.Context](utils.Empty),
LetTo[context.Context](utils.SetLastName, "Doe"),
LetTo[context.Context](utils.SetGivenName, "John"),
Map[context.Context](utils.GetFullName),
)
assert.Equal(t, res(context.Background()), O.Of("John Doe"))
}
func TestBindTo(t *testing.T) {
type State struct {
Value int
}
res := F.Pipe1(
Of[context.Context](42),
BindTo[context.Context](func(v int) State {
return State{Value: v}
}),
)
assert.Equal(t, res(context.Background()), O.Of(State{Value: 42}))
}

113
v2/readeroption/curry.go Normal file
View File

@@ -0,0 +1,113 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
G "github.com/IBM/fp-go/v2/readeroption/generic"
)
// Curry functions convert Go functions that take a context as the first parameter
// and return (value, bool) into curried ReaderOption functions.
//
// This follows the Go convention of passing context as the first parameter
// (see https://pkg.go.dev/context), while providing a functional programming interface.
//
// The bool return value indicates success (true) or failure (false), which maps to
// Some or None in the Option monad.
// Curry0 converts a function that takes only a context and returns (A, bool)
// into a ReaderOption[R, A].
//
// Example:
//
// getConfig := func(ctx context.Context) (Config, bool) {
// cfg, ok := ctx.Value("config").(Config)
// return cfg, ok
// }
// ro := readeroption.Curry0(getConfig)
// result := ro(ctx) // Returns option.Some(config) or option.None()
func Curry0[R, A any](f func(R) (A, bool)) ReaderOption[R, A] {
return G.Curry0[ReaderOption[R, A]](f)
}
// Curry1 converts a function that takes a context and one argument, returning (A, bool),
// into a curried function that returns a ReaderOption.
//
// Example:
//
// findUser := func(ctx context.Context, id int) (User, bool) {
// // Query database using context
// return user, found
// }
// ro := readeroption.Curry1(findUser)
// result := ro(123)(ctx) // Returns option.Some(user) or option.None()
func Curry1[R, T1, A any](f func(R, T1) (A, bool)) Kleisli[R, T1, A] {
return G.Curry1[ReaderOption[R, A]](f)
}
// Curry2 converts a function that takes a context and two arguments, returning (A, bool),
// into a curried function that returns a ReaderOption.
//
// Example:
//
// query := func(ctx context.Context, table string, id int) (Record, bool) {
// // Query database using context
// return record, found
// }
// ro := readeroption.Curry2(query)
// result := ro("users")(123)(ctx) // Returns option.Some(record) or option.None()
func Curry2[R, T1, T2, A any](f func(R, T1, T2) (A, bool)) func(T1) func(T2) ReaderOption[R, A] {
return G.Curry2[ReaderOption[R, A]](f)
}
// Curry3 converts a function that takes a context and three arguments, returning (A, bool),
// into a curried function that returns a ReaderOption.
//
// Example:
//
// complexQuery := func(ctx context.Context, db string, table string, id int) (Record, bool) {
// // Query database using context
// return record, found
// }
// ro := readeroption.Curry3(complexQuery)
// result := ro("mydb")("users")(123)(ctx)
func Curry3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, bool)) func(T1) func(T2) func(T3) ReaderOption[R, A] {
return G.Curry3[ReaderOption[R, A]](f)
}
// Uncurry1 converts a curried ReaderOption function back to a Go function
// that takes a context and one argument, returning (A, bool).
//
// Example:
//
// ro := func(id int) readeroption.ReaderOption[context.Context, User] { ... }
// findUser := readeroption.Uncurry1(ro)
// user, found := findUser(ctx, 123)
func Uncurry1[R, T1, A any](f func(T1) ReaderOption[R, A]) func(R, T1) (A, bool) {
return G.Uncurry1(f)
}
// Uncurry2 converts a curried ReaderOption function back to a Go function
// that takes a context and two arguments, returning (A, bool).
func Uncurry2[R, T1, T2, A any](f func(T1) func(T2) ReaderOption[R, A]) func(R, T1, T2) (A, bool) {
return G.Uncurry2(f)
}
// Uncurry3 converts a curried ReaderOption function back to a Go function
// that takes a context and three arguments, returning (A, bool).
func Uncurry3[R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) ReaderOption[R, A]) func(R, T1, T2, T3) (A, bool) {
return G.Uncurry3(f)
}

View File

@@ -0,0 +1,164 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
"context"
"testing"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
func TestCurry0(t *testing.T) {
// Function that returns a value from context
getConfig := func(ctx context.Context) (string, bool) {
if val := ctx.Value("config"); val != nil {
return val.(string), true
}
return "", false
}
ro := Curry0(getConfig)
// Test with value in context
ctx1 := context.WithValue(context.Background(), "config", "test-config")
result1 := ro(ctx1)
assert.Equal(t, O.Of("test-config"), result1)
// Test without value in context
ctx2 := context.Background()
result2 := ro(ctx2)
assert.Equal(t, O.None[string](), result2)
}
func TestCurry1(t *testing.T) {
// Function that looks up a value by key
lookup := func(ctx context.Context, key string) (int, bool) {
if val := ctx.Value(key); val != nil {
return val.(int), true
}
return 0, false
}
ro := Curry1(lookup)
// Test with value in context
ctx1 := context.WithValue(context.Background(), "count", 42)
result1 := ro("count")(ctx1)
assert.Equal(t, O.Of(42), result1)
// Test without value in context
ctx2 := context.Background()
result2 := ro("count")(ctx2)
assert.Equal(t, O.None[int](), result2)
}
func TestCurry2(t *testing.T) {
// Function that combines two parameters with context
combine := func(ctx context.Context, a string, b int) (string, bool) {
if ctx.Value("enabled") == true {
return a + ":" + string(rune('0'+b)), true
}
return "", false
}
ro := Curry2(combine)
// Test with enabled context
ctx1 := context.WithValue(context.Background(), "enabled", true)
result1 := ro("test")(5)(ctx1)
assert.Equal(t, O.Of("test:5"), result1)
// Test with disabled context
ctx2 := context.Background()
result2 := ro("test")(5)(ctx2)
assert.Equal(t, O.None[string](), result2)
}
func TestCurry3(t *testing.T) {
// Function that combines three parameters with context
combine := func(ctx context.Context, a string, b int, c bool) (string, bool) {
if ctx.Value("enabled") == true && c {
return a + ":" + string(rune('0'+b)), true
}
return "", false
}
ro := Curry3(combine)
// Test with enabled context and true flag
ctx1 := context.WithValue(context.Background(), "enabled", true)
result1 := ro("test")(5)(true)(ctx1)
assert.Equal(t, O.Of("test:5"), result1)
// Test with false flag
result2 := ro("test")(5)(false)(ctx1)
assert.Equal(t, O.None[string](), result2)
}
func TestUncurry1(t *testing.T) {
// Create a curried function
curried := func(x int) ReaderOption[context.Context, int] {
return Of[context.Context](x * 2)
}
// Uncurry it
uncurried := Uncurry1(curried)
// Test the uncurried function
result, ok := uncurried(context.Background(), 21)
assert.True(t, ok)
assert.Equal(t, 42, result)
}
func TestUncurry2(t *testing.T) {
// Create a curried function
curried := func(x int) func(y int) ReaderOption[context.Context, int] {
return func(y int) ReaderOption[context.Context, int] {
return Of[context.Context](x + y)
}
}
// Uncurry it
uncurried := Uncurry2(curried)
// Test the uncurried function
result, ok := uncurried(context.Background(), 10, 32)
assert.True(t, ok)
assert.Equal(t, 42, result)
}
func TestUncurry3(t *testing.T) {
// Create a curried function
curried := func(x int) func(y int) func(z int) ReaderOption[context.Context, int] {
return func(y int) func(z int) ReaderOption[context.Context, int] {
return func(z int) ReaderOption[context.Context, int] {
return Of[context.Context](x + y + z)
}
}
}
// Uncurry it
uncurried := Uncurry3(curried)
// Test the uncurried function
result, ok := uncurried(context.Background(), 10, 20, 12)
assert.True(t, ok)
assert.Equal(t, 42, result)
}
// Made with Bob

98
v2/readeroption/from.go Normal file
View File

@@ -0,0 +1,98 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
G "github.com/IBM/fp-go/v2/readeroption/generic"
)
// From functions convert Go functions that take a context as the first parameter
// and return (value, bool) into ReaderOption functions with uncurried parameters.
//
// Unlike Curry functions which return fully curried functions, From functions
// return functions that take multiple parameters at once (uncurried style).
//
// This follows the Go convention of passing context as the first parameter
// (see https://pkg.go.dev/context), while providing a functional programming interface.
//
// The bool return value indicates success (true) or failure (false), which maps to
// Some or None in the Option monad.
// From0 converts a function that takes only a context and returns (A, bool)
// into a function that returns a ReaderOption[R, A].
//
// Example:
//
// getConfig := func(ctx context.Context) (Config, bool) {
// cfg, ok := ctx.Value("config").(Config)
// return cfg, ok
// }
// roFunc := readeroption.From0(getConfig)
// ro := roFunc() // Returns a ReaderOption[context.Context, Config]
// result := ro(ctx) // Returns option.Some(config) or option.None()
func From0[R, A any](f func(R) (A, bool)) func() ReaderOption[R, A] {
return G.From0[ReaderOption[R, A]](f)
}
// From1 converts a function that takes a context and one argument, returning (A, bool),
// into a function that takes one argument and returns a ReaderOption.
//
// This is equivalent to Curry1 but provided for consistency with the From naming convention.
//
// Example:
//
// findUser := func(ctx context.Context, id int) (User, bool) {
// // Query database using context
// return user, found
// }
// roFunc := readeroption.From1(findUser)
// ro := roFunc(123) // Returns a ReaderOption[context.Context, User]
// result := ro(ctx) // Returns option.Some(user) or option.None()
func From1[R, T1, A any](f func(R, T1) (A, bool)) Kleisli[R, T1, A] {
return G.From1[ReaderOption[R, A]](f)
}
// From2 converts a function that takes a context and two arguments, returning (A, bool),
// into a function that takes two arguments (uncurried) and returns a ReaderOption.
//
// Example:
//
// query := func(ctx context.Context, table string, id int) (Record, bool) {
// // Query database using context
// return record, found
// }
// roFunc := readeroption.From2(query)
// ro := roFunc("users", 123) // Returns a ReaderOption[context.Context, Record]
// result := ro(ctx) // Returns option.Some(record) or option.None()
func From2[R, T1, T2, A any](f func(R, T1, T2) (A, bool)) func(T1, T2) ReaderOption[R, A] {
return G.From2[ReaderOption[R, A]](f)
}
// From3 converts a function that takes a context and three arguments, returning (A, bool),
// into a function that takes three arguments (uncurried) and returns a ReaderOption.
//
// Example:
//
// complexQuery := func(ctx context.Context, db string, table string, id int) (Record, bool) {
// // Query database using context
// return record, found
// }
// roFunc := readeroption.From3(complexQuery)
// ro := roFunc("mydb", "users", 123) // Returns a ReaderOption[context.Context, Record]
// result := ro(ctx) // Returns option.Some(record) or option.None()
func From3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, bool)) func(T1, T2, T3) ReaderOption[R, A] {
return G.From3[ReaderOption[R, A]](f)
}

View File

@@ -0,0 +1,114 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
"context"
"testing"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
func TestFrom0(t *testing.T) {
// Function that returns a value from context
getConfig := func(ctx context.Context) (string, bool) {
if val := ctx.Value("config"); val != nil {
return val.(string), true
}
return "", false
}
roFunc := From0(getConfig)
ro := roFunc()
// Test with value in context
ctx1 := context.WithValue(context.Background(), "config", "test-config")
result1 := ro(ctx1)
assert.Equal(t, O.Of("test-config"), result1)
// Test without value in context
ctx2 := context.Background()
result2 := ro(ctx2)
assert.Equal(t, O.None[string](), result2)
}
func TestFrom1(t *testing.T) {
// Function that looks up a value by key
lookup := func(ctx context.Context, key string) (int, bool) {
if val := ctx.Value(key); val != nil {
return val.(int), true
}
return 0, false
}
roFunc := From1(lookup)
// Test with value in context
ctx1 := context.WithValue(context.Background(), "count", 42)
result1 := roFunc("count")(ctx1)
assert.Equal(t, O.Of(42), result1)
// Test without value in context
ctx2 := context.Background()
result2 := roFunc("count")(ctx2)
assert.Equal(t, O.None[int](), result2)
}
func TestFrom2(t *testing.T) {
// Function that combines two parameters with context
combine := func(ctx context.Context, a string, b int) (string, bool) {
if ctx.Value("enabled") == true {
return a + ":" + string(rune('0'+b)), true
}
return "", false
}
roFunc := From2(combine)
// Test with enabled context
ctx1 := context.WithValue(context.Background(), "enabled", true)
result1 := roFunc("test", 5)(ctx1)
assert.Equal(t, O.Of("test:5"), result1)
// Test with disabled context
ctx2 := context.Background()
result2 := roFunc("test", 5)(ctx2)
assert.Equal(t, O.None[string](), result2)
}
func TestFrom3(t *testing.T) {
// Function that combines three parameters with context
combine := func(ctx context.Context, a string, b int, c bool) (string, bool) {
if ctx.Value("enabled") == true && c {
return a + ":" + string(rune('0'+b)), true
}
return "", false
}
roFunc := From3(combine)
// Test with enabled context and true flag
ctx1 := context.WithValue(context.Background(), "enabled", true)
result1 := roFunc("test", 5, true)(ctx1)
assert.Equal(t, O.Of("test:5"), result1)
// Test with false flag
result2 := roFunc("test", 5, false)(ctx1)
assert.Equal(t, O.None[string](), result2)
}
// Made with Bob

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package generic
import (
F "github.com/IBM/fp-go/v2/function"
RA "github.com/IBM/fp-go/v2/internal/array"
O "github.com/IBM/fp-go/v2/option"
)
// MonadTraverseArray transforms an array
func MonadTraverseArray[GB ~func(E) O.Option[B], GBS ~func(E) O.Option[BBS], AAS ~[]A, BBS ~[]B, E, A, B any](ma AAS, f func(A) GB) GBS {
return RA.MonadTraverse(
Of[GBS, E, BBS],
Map[GBS, func(E) O.Option[func(B) BBS], E, BBS, func(B) BBS],
Ap[GB, GBS, func(E) O.Option[func(B) BBS], E, B, BBS],
ma, f,
)
}
// TraverseArray transforms an array
func TraverseArray[GB ~func(E) O.Option[B], GBS ~func(E) O.Option[BBS], AAS ~[]A, BBS ~[]B, E, A, B any](f func(A) GB) func(AAS) GBS {
return RA.Traverse[AAS](
Of[GBS, E, BBS],
Map[GBS, func(E) O.Option[func(B) BBS], E, BBS, func(B) BBS],
Ap[GB, GBS, func(E) O.Option[func(B) BBS], E, B, BBS],
f,
)
}
// TraverseArrayWithIndex transforms an array
func TraverseArrayWithIndex[GB ~func(E) O.Option[B], GBS ~func(E) O.Option[BBS], AAS ~[]A, BBS ~[]B, E, A, B any](f func(int, A) GB) func(AAS) GBS {
return RA.TraverseWithIndex[AAS](
Of[GBS, E, BBS],
Map[GBS, func(E) O.Option[func(B) BBS], E, BBS, func(B) BBS],
Ap[GB, GBS, func(E) O.Option[func(B) BBS], E, B, BBS],
f,
)
}
// SequenceArray converts a homogeneous sequence of either into an either of sequence
func SequenceArray[GA ~func(E) O.Option[A], GAS ~func(E) O.Option[AAS], AAS ~[]A, GAAS ~[]GA, E, A any](ma GAAS) GAS {
return MonadTraverseArray[GA, GAS](ma, F.Identity[GA])
}

View File

@@ -0,0 +1,184 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package generic
import (
A "github.com/IBM/fp-go/v2/internal/apply"
C "github.com/IBM/fp-go/v2/internal/chain"
F "github.com/IBM/fp-go/v2/internal/functor"
O "github.com/IBM/fp-go/v2/option"
)
// Do creates an empty context of type [S] to be used with the [Bind] operation.
// This is the starting point for do-notation style composition.
//
// Example:
//
// type State struct {
// Config Config
// User User
// }
// type Env struct {
// ConfigService ConfigService
// UserService UserService
// }
// result := generic.Do[ReaderEither[Env, error, State], Env, error, State](State{})
func Do[GS ~func(R) O.Option[S], R, S any](
empty S,
) GS {
return Of[GS](empty)
}
// Bind attaches the result of a computation to a context [S1] to produce a context [S2].
// This enables sequential composition where each step can depend on the results of previous steps
// and access the shared environment.
//
// The setter function takes the result of the computation and returns a function that
// updates the context from S1 to S2.
//
// Example:
//
// type State struct {
// Config Config
// User User
// }
// type Env struct {
// ConfigService ConfigService
// UserService UserService
// }
//
// result := F.Pipe2(
// generic.Do[ReaderEither[Env, error, State], Env, error, State](State{}),
// generic.Bind[ReaderEither[Env, error, State], ReaderEither[Env, error, State], ReaderEither[Env, error, Config], Env, error, State, State, Config](
// func(cfg Config) func(State) State {
// return func(s State) State { s.Config = cfg; return s }
// },
// func(s State) ReaderEither[Env, error, Config] {
// return func(env Env) either.Either[error, Config] {
// return env.ConfigService.Load()
// }
// },
// ),
// generic.Bind[ReaderEither[Env, error, State], ReaderEither[Env, error, State], ReaderEither[Env, error, User], Env, error, State, State, User](
// func(user User) func(State) State {
// return func(s State) State { s.User = user; return s }
// },
// func(s State) ReaderEither[Env, error, User] {
// // This can access s.Config from the previous step
// return func(env Env) either.Either[error, User] {
// return env.UserService.GetUserForConfig(s.Config)
// }
// },
// ),
// )
func Bind[GS1 ~func(R) O.Option[S1], GS2 ~func(R) O.Option[S2], GT ~func(R) O.Option[T], R, S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) GT,
) func(GS1) GS2 {
return C.Bind(
Chain[GS1, GS2, R, S1, S2],
Map[GT, GS2, R, T, S2],
setter,
f,
)
}
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
func Let[GS1 ~func(R) O.Option[S1], GS2 ~func(R) O.Option[S2], R, S1, S2, T any](
key func(T) func(S1) S2,
f func(S1) T,
) func(GS1) GS2 {
return F.Let(
Map[GS1, GS2, R, S1, S2],
key,
f,
)
}
// LetTo attaches the a value to a context [S1] to produce a context [S2]
func LetTo[GS1 ~func(R) O.Option[S1], GS2 ~func(R) O.Option[S2], R, S1, S2, B any](
key func(B) func(S1) S2,
b B,
) func(GS1) GS2 {
return F.LetTo(
Map[GS1, GS2, R, S1, S2],
key,
b,
)
}
// BindTo initializes a new state [S1] from a value [T]
func BindTo[GS1 ~func(R) O.Option[S1], GT ~func(R) O.Option[T], R, S1, T any](
setter func(T) S1,
) func(GT) GS1 {
return C.BindTo(
Map[GT, GS1, R, T, S1],
setter,
)
}
// ApS attaches a value to a context [S1] to produce a context [S2] by considering
// the context and the value concurrently (using Applicative rather than Monad).
// This allows independent computations to be combined without one depending on the result of the other.
//
// Unlike Bind, which sequences operations, ApS can be used when operations are independent
// and can conceptually run in parallel.
//
// Example:
//
// type State struct {
// Config Config
// User User
// }
// type Env struct {
// ConfigService ConfigService
// UserService UserService
// }
//
// // These operations are independent and can be combined with ApS
// getConfig := func(env Env) either.Either[error, Config] {
// return env.ConfigService.Load()
// }
// getUser := func(env Env) either.Either[error, User] {
// return env.UserService.GetCurrent()
// }
//
// result := F.Pipe2(
// generic.Do[ReaderEither[Env, error, State], Env, error, State](State{}),
// generic.ApS[...](
// func(cfg Config) func(State) State {
// return func(s State) State { s.Config = cfg; return s }
// },
// getConfig,
// ),
// generic.ApS[...](
// func(user User) func(State) State {
// return func(s State) State { s.User = user; return s }
// },
// getUser,
// ),
// )
func ApS[GS1 ~func(R) O.Option[S1], GS2 ~func(R) O.Option[S2], GT ~func(R) O.Option[T], R, S1, S2, T any](
setter func(T) func(S1) S2,
fa GT,
) func(GS1) GS2 {
return A.ApS(
Ap[GT, GS2, func(R) O.Option[func(T) S2], R, T, S2],
Map[GS1, func(R) O.Option[func(T) S2], R, S1, func(T) S2],
setter,
fa,
)
}

View File

@@ -0,0 +1,52 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package generic
import (
O "github.com/IBM/fp-go/v2/option"
G "github.com/IBM/fp-go/v2/reader/generic"
)
// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func Curry0[GEA ~func(R) O.Option[A], R, A any](f func(R) (A, bool)) GEA {
return G.Curry0[GEA](O.Optionize1(f))
}
func Curry1[GEA ~func(R) O.Option[A], R, T1, A any](f func(R, T1) (A, bool)) func(T1) GEA {
return G.Curry1[GEA](O.Optionize2(f))
}
func Curry2[GEA ~func(R) O.Option[A], R, T1, T2, A any](f func(R, T1, T2) (A, bool)) func(T1) func(T2) GEA {
return G.Curry2[GEA](O.Optionize3(f))
}
func Curry3[GEA ~func(R) O.Option[A], R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, bool)) func(T1) func(T2) func(T3) GEA {
return G.Curry3[GEA](O.Optionize4(f))
}
func Uncurry1[GEA ~func(R) O.Option[A], R, T1, A any](f func(T1) GEA) func(R, T1) (A, bool) {
return O.Unoptionize2(G.Uncurry1(f))
}
func Uncurry2[GEA ~func(R) O.Option[A], R, T1, T2, A any](f func(T1) func(T2) GEA) func(R, T1, T2) (A, bool) {
return O.Unoptionize3(G.Uncurry2(f))
}
func Uncurry3[GEA ~func(R) O.Option[A], R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) GEA) func(R, T1, T2, T3) (A, bool) {
return O.Unoptionize4(G.Uncurry3(f))
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package generic
import (
O "github.com/IBM/fp-go/v2/option"
G "github.com/IBM/fp-go/v2/reader/generic"
)
// these functions From a golang function with the context as the firsr parameter into a either reader with the context as the last parameter
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func From0[GEA ~func(R) O.Option[A], R, A any](f func(R) (A, bool)) func() GEA {
return G.From0[GEA](O.Optionize1(f))
}
func From1[GEA ~func(R) O.Option[A], R, T1, A any](f func(R, T1) (A, bool)) func(T1) GEA {
return G.From1[GEA](O.Optionize2(f))
}
func From2[GEA ~func(R) O.Option[A], R, T1, T2, A any](f func(R, T1, T2) (A, bool)) func(T1, T2) GEA {
return G.From2[GEA](O.Optionize3(f))
}
func From3[GEA ~func(R) O.Option[A], R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, bool)) func(T1, T2, T3) GEA {
return G.From3[GEA](O.Optionize4(f))
}

View File

@@ -0,0 +1,129 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package generic
import (
F "github.com/IBM/fp-go/v2/function"
FO "github.com/IBM/fp-go/v2/internal/fromoption"
FR "github.com/IBM/fp-go/v2/internal/fromreader"
FC "github.com/IBM/fp-go/v2/internal/functor"
"github.com/IBM/fp-go/v2/internal/optiont"
"github.com/IBM/fp-go/v2/internal/readert"
O "github.com/IBM/fp-go/v2/option"
R "github.com/IBM/fp-go/v2/reader/generic"
)
//go:inline
func MakeReaderOption[GEA ~func(E) O.Option[A], E, A any](f func(E) O.Option[A]) GEA {
return f
}
//go:inline
func FromOption[GEA ~func(E) O.Option[A], E, A any](e O.Option[A]) GEA {
return R.Of[GEA](e)
}
func SomeReader[GA ~func(E) A, GEA ~func(E) O.Option[A], E, A any](r GA) GEA {
return optiont.SomeF(R.MonadMap[GA, GEA, E, A, O.Option[A]], r)
}
func Some[GEA ~func(E) O.Option[A], E, A any](r A) GEA {
return optiont.Of(R.Of[GEA, E, O.Option[A]], r)
}
//go:inline
func FromReader[GA ~func(E) A, GEA ~func(E) O.Option[A], E, A any](r GA) GEA {
return SomeReader[GA, GEA](r)
}
func MonadMap[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](fa GEA, f func(A) B) GEB {
return readert.MonadMap[GEA, GEB](O.MonadMap[A, B], fa, f)
}
func Map[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](f func(A) B) func(GEA) GEB {
return readert.Map[GEA, GEB](O.Map[A, B], f)
}
func MonadChain[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](ma GEA, f func(A) GEB) GEB {
return readert.MonadChain(O.MonadChain[A, B], ma, f)
}
func Chain[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](f func(A) GEB) func(GEA) GEB {
return F.Bind2nd(MonadChain[GEA, GEB, E, A, B], f)
}
func Of[GEA ~func(E) O.Option[A], E, A any](a A) GEA {
return readert.MonadOf[GEA](O.Of[A], a)
}
func MonadAp[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], GEFAB ~func(E) O.Option[func(A) B], E, A, B any](fab GEFAB, fa GEA) GEB {
return readert.MonadAp[GEA, GEB, GEFAB, E, A](O.MonadAp[B, A], fab, fa)
}
func Ap[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], GEFAB ~func(E) O.Option[func(A) B], E, A, B any](fa GEA) func(GEFAB) GEB {
return F.Bind2nd(MonadAp[GEA, GEB, GEFAB, E, A, B], fa)
}
func FromPredicate[GEA ~func(E) O.Option[A], E, A any](pred func(A) bool) func(A) GEA {
return FO.FromPredicate(FromOption[GEA, E, A], pred)
}
func Fold[GEA ~func(E) O.Option[A], GB ~func(E) B, E, A, B any](onNone func() GB, onRight func(A) GB) func(GEA) GB {
return optiont.MatchE(R.MonadChain[GEA, GB, E, O.Option[A], B], onNone, onRight)
}
func GetOrElse[GEA ~func(E) O.Option[A], GA ~func(E) A, E, A any](onNone func() GA) func(GEA) GA {
return optiont.GetOrElse(R.MonadChain[GEA, GA, E, O.Option[A], A], onNone, R.Of[GA, E, A])
}
func Ask[GEE ~func(E) O.Option[E], E, L any]() GEE {
return FR.Ask(FromReader[func(E) E, GEE, E, E])()
}
func Asks[GA ~func(E) A, GEA ~func(E) O.Option[A], E, A any](r GA) GEA {
return FR.Asks(FromReader[GA, GEA, E, A])(r)
}
func MonadChainOptionK[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](ma GEA, f func(A) O.Option[B]) GEB {
return FO.MonadChainOptionK(
MonadChain[GEA, GEB, E, A, B],
FromOption[GEB, E, B],
ma,
f,
)
}
func ChainOptionK[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](f func(A) O.Option[B]) func(ma GEA) GEB {
return F.Bind2nd(MonadChainOptionK[GEA, GEB, E, A, B], f)
}
func Flatten[GEA ~func(E) O.Option[A], GGA ~func(E) O.Option[GEA], E, A any](mma GGA) GEA {
return MonadChain(mma, F.Identity[GEA])
}
// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s
// `contramap`).
func Local[GA1 ~func(R1) O.Option[A], GA2 ~func(R2) O.Option[A], R2, R1, E, A any](f func(R2) R1) func(GA1) GA2 {
return R.Local[GA1, GA2](f)
}
func MonadFlap[GEFAB ~func(E) O.Option[func(A) B], GEB ~func(E) O.Option[B], E, A, B any](fab GEFAB, a A) GEB {
return FC.MonadFlap(MonadMap[GEFAB, GEB], fab, a)
}
func Flap[GEFAB ~func(E) O.Option[func(A) B], GEB ~func(E) O.Option[B], E, A, B any](a A) func(GEFAB) GEB {
return FC.Flap(Map[GEFAB, GEB], a)
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package generic
import (
"github.com/IBM/fp-go/v2/internal/apply"
O "github.com/IBM/fp-go/v2/option"
T "github.com/IBM/fp-go/v2/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[
GA ~func(E) O.Option[A],
GTA ~func(E) O.Option[T.Tuple1[A]],
E, A any](a GA) GTA {
return apply.SequenceT1(
Map[GA, GTA, E, A, T.Tuple1[A]],
a,
)
}
func SequenceT2[
GA ~func(E) O.Option[A],
GB ~func(E) O.Option[B],
GTAB ~func(E) O.Option[T.Tuple2[A, B]],
E, A, B any](a GA, b GB) GTAB {
return apply.SequenceT2(
Map[GA, func(E) O.Option[func(B) T.Tuple2[A, B]], E, A, func(B) T.Tuple2[A, B]],
Ap[GB, GTAB, func(E) O.Option[func(B) T.Tuple2[A, B]], E, B, T.Tuple2[A, B]],
a, b,
)
}
func SequenceT3[
GA ~func(E) O.Option[A],
GB ~func(E) O.Option[B],
GC ~func(E) O.Option[C],
GTABC ~func(E) O.Option[T.Tuple3[A, B, C]],
E, A, B, C any](a GA, b GB, c GC) GTABC {
return apply.SequenceT3(
Map[GA, func(E) O.Option[func(B) func(C) T.Tuple3[A, B, C]], E, A, func(B) func(C) T.Tuple3[A, B, C]],
Ap[GB, func(E) O.Option[func(C) T.Tuple3[A, B, C]], func(E) O.Option[func(B) func(C) T.Tuple3[A, B, C]], E, B, func(C) T.Tuple3[A, B, C]],
Ap[GC, GTABC, func(E) O.Option[func(C) T.Tuple3[A, B, C]], E, C, T.Tuple3[A, B, C]],
a, b, c,
)
}
func SequenceT4[
GA ~func(E) O.Option[A],
GB ~func(E) O.Option[B],
GC ~func(E) O.Option[C],
GD ~func(E) O.Option[D],
GTABCD ~func(E) O.Option[T.Tuple4[A, B, C, D]],
E, A, B, C, D any](a GA, b GB, c GC, d GD) GTABCD {
return apply.SequenceT4(
Map[GA, func(E) O.Option[func(B) func(C) func(D) T.Tuple4[A, B, C, D]], E, A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]],
Ap[GB, func(E) O.Option[func(C) func(D) T.Tuple4[A, B, C, D]], func(E) O.Option[func(B) func(C) func(D) T.Tuple4[A, B, C, D]], E, B, func(C) func(D) T.Tuple4[A, B, C, D]],
Ap[GC, func(E) O.Option[func(D) T.Tuple4[A, B, C, D]], func(E) O.Option[func(C) func(D) T.Tuple4[A, B, C, D]], E, C, func(D) T.Tuple4[A, B, C, D]],
Ap[GD, GTABCD, func(E) O.Option[func(D) T.Tuple4[A, B, C, D]], E, D, T.Tuple4[A, B, C, D]],
a, b, c, d,
)
}

292
v2/readeroption/reader.go Normal file
View File

@@ -0,0 +1,292 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
"github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/fromoption"
"github.com/IBM/fp-go/v2/internal/fromreader"
"github.com/IBM/fp-go/v2/internal/functor"
"github.com/IBM/fp-go/v2/internal/optiont"
"github.com/IBM/fp-go/v2/internal/readert"
O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader"
)
// FromOption lifts an Option[A] into a ReaderOption[E, A].
// The resulting computation ignores the environment and returns the given option.
//
//go:inline
func FromOption[E, A any](e Option[A]) ReaderOption[E, A] {
return reader.Of[E](e)
}
// Some wraps a value in a ReaderOption, representing a successful computation.
// This is equivalent to Of but more explicit about the Option semantics.
func Some[E, A any](r A) ReaderOption[E, A] {
return optiont.Of(reader.Of[E, Option[A]], r)
}
// FromReader lifts a Reader[E, A] into a ReaderOption[E, A].
// The resulting computation always succeeds (returns Some).
//
//go:inline
func FromReader[E, A any](r Reader[E, A]) ReaderOption[E, A] {
return SomeReader(r)
}
// SomeReader lifts a Reader[E, A] into a ReaderOption[E, A].
// The resulting computation always succeeds (returns Some).
func SomeReader[E, A any](r Reader[E, A]) ReaderOption[E, A] {
return optiont.SomeF(reader.MonadMap[E, A, Option[A]], r)
}
// MonadMap applies a function to the value inside a ReaderOption.
// If the ReaderOption contains None, the function is not applied.
//
// Example:
//
// ro := readeroption.Of[Config](42)
// doubled := readeroption.MonadMap(ro, func(x int) int { return x * 2 })
func MonadMap[E, A, B any](fa ReaderOption[E, A], f func(A) B) ReaderOption[E, B] {
return readert.MonadMap[ReaderOption[E, A], ReaderOption[E, B]](O.MonadMap[A, B], fa, f)
}
// Map returns a function that applies a transformation to the value inside a ReaderOption.
// This is the curried version of MonadMap, useful for composition with F.Pipe.
//
// Example:
//
// doubled := F.Pipe1(
// readeroption.Of[Config](42),
// readeroption.Map[Config](func(x int) int { return x * 2 }),
// )
func Map[E, A, B any](f func(A) B) Operator[E, A, B] {
return readert.Map[ReaderOption[E, A], ReaderOption[E, B]](O.Map[A, B], f)
}
// MonadChain sequences two ReaderOption computations, where the second depends on the result of the first.
// If the first computation returns None, the second is not executed.
//
// Example:
//
// findUser := func(id int) readeroption.ReaderOption[DB, User] { ... }
// loadProfile := func(user User) readeroption.ReaderOption[DB, Profile] { ... }
// result := readeroption.MonadChain(findUser(123), loadProfile)
func MonadChain[E, A, B any](ma ReaderOption[E, A], f Kleisli[E, A, B]) ReaderOption[E, B] {
return readert.MonadChain(O.MonadChain[A, B], ma, f)
}
// Chain returns a function that sequences ReaderOption computations.
// This is the curried version of MonadChain, useful for composition with F.Pipe.
//
// Example:
//
// result := F.Pipe1(
// findUser(123),
// readeroption.Chain(loadProfile),
// )
func Chain[E, A, B any](f Kleisli[E, A, B]) Operator[E, A, B] {
return readert.Chain[ReaderOption[E, A]](O.Chain[A, B], f)
}
// Of wraps a value in a ReaderOption, representing a successful computation.
// The resulting computation ignores the environment and returns Some(a).
//
// Example:
//
// ro := readeroption.Of[Config](42)
// result := ro(config) // Returns option.Some(42)
func Of[E, A any](a A) ReaderOption[E, A] {
return readert.MonadOf[ReaderOption[E, A]](O.Of[A], a)
}
// None creates a ReaderOption representing a failed computation.
// The resulting computation ignores the environment and returns None.
//
// Example:
//
// ro := readeroption.None[Config, int]()
// result := ro(config) // Returns option.None[int]()
func None[E, A any]() ReaderOption[E, A] {
return reader.Of[E](O.None[A]())
}
// MonadAp applies a function wrapped in a ReaderOption to a value wrapped in a ReaderOption.
// Both computations are executed with the same environment.
// If either computation returns None, the result is None.
func MonadAp[E, A, B any](fab ReaderOption[E, func(A) B], fa ReaderOption[E, A]) ReaderOption[E, B] {
return readert.MonadAp[ReaderOption[E, A], ReaderOption[E, B], ReaderOption[E, func(A) B], E, A](O.MonadAp[B, A], fab, fa)
}
// Ap returns a function that applies a function wrapped in a ReaderOption to a value.
// This is the curried version of MonadAp.
func Ap[B, E, A any](fa ReaderOption[E, A]) Operator[E, func(A) B, B] {
return readert.Ap[ReaderOption[E, A], ReaderOption[E, B], ReaderOption[E, func(A) B], E, A](O.Ap[B, A], fa)
}
// FromPredicate creates a Kleisli arrow that filters a value based on a predicate.
// If the predicate returns true, the value is wrapped in Some; otherwise, None is returned.
//
// Example:
//
// isPositive := readeroption.FromPredicate[Config](func(x int) bool { return x > 0 })
// result := F.Pipe1(
// readeroption.Of[Config](42),
// readeroption.Chain(isPositive),
// )
func FromPredicate[E, A any](pred func(A) bool) Kleisli[E, A, A] {
return fromoption.FromPredicate(FromOption[E, A], pred)
}
// Fold extracts the value from a ReaderOption by providing handlers for both cases.
// The onNone handler is called if the computation returns None.
// The onRight handler is called if the computation returns Some(a).
//
// Example:
//
// result := readeroption.Fold(
// func() reader.Reader[Config, string] { return reader.Of[Config]("not found") },
// func(user User) reader.Reader[Config, string] { return reader.Of[Config](user.Name) },
// )(findUser(123))
func Fold[E, A, B any](onNone func() Reader[E, B], onRight func(A) Reader[E, B]) func(ReaderOption[E, A]) Reader[E, B] {
return optiont.MatchE(reader.MonadChain[E, Option[A], B], onNone, onRight)
}
// GetOrElse returns the value from a ReaderOption, or a default value if it's None.
//
// Example:
//
// result := readeroption.GetOrElse(
// func() reader.Reader[Config, User] { return reader.Of[Config](defaultUser) },
// )(findUser(123))
func GetOrElse[E, A any](onNone func() Reader[E, A]) func(ReaderOption[E, A]) Reader[E, A] {
return optiont.GetOrElse(reader.MonadChain[E, Option[A], A], onNone, reader.Of[E, A])
}
// Ask retrieves the current environment as a ReaderOption.
// This always succeeds and returns Some(environment).
//
// Example:
//
// getConfig := readeroption.Ask[Config, any]()
// result := getConfig(myConfig) // Returns option.Some(myConfig)
func Ask[E, L any]() ReaderOption[E, E] {
return fromreader.Ask(FromReader[E, E])()
}
// Asks creates a ReaderOption that applies a function to the environment.
// This always succeeds and returns Some(f(environment)).
//
// Example:
//
// getTimeout := readeroption.Asks(func(cfg Config) int { return cfg.Timeout })
// result := getTimeout(myConfig) // Returns option.Some(myConfig.Timeout)
func Asks[E, A any](r Reader[E, A]) ReaderOption[E, A] {
return fromreader.Asks(FromReader[E, A])(r)
}
// MonadChainOptionK chains a ReaderOption with a function that returns an Option.
// This is useful for integrating functions that return Option directly.
//
// Example:
//
// parseAge := func(s string) option.Option[int] { ... }
// result := readeroption.MonadChainOptionK(
// readeroption.Of[Config]("25"),
// parseAge,
// )
func MonadChainOptionK[E, A, B any](ma ReaderOption[E, A], f func(A) Option[B]) ReaderOption[E, B] {
return fromoption.MonadChainOptionK(
MonadChain[E, A, B],
FromOption[E, B],
ma,
f,
)
}
// ChainOptionK returns a function that chains a ReaderOption with a function returning an Option.
// This is the curried version of MonadChainOptionK.
//
// Example:
//
// parseAge := func(s string) option.Option[int] { ... }
// result := F.Pipe1(
// readeroption.Of[Config]("25"),
// readeroption.ChainOptionK[Config](parseAge),
// )
func ChainOptionK[E, A, B any](f func(A) Option[B]) Operator[E, A, B] {
return fromoption.ChainOptionK(
Chain[E, A, B],
FromOption[E, B],
f,
)
}
// Flatten removes one level of nesting from a ReaderOption.
// Converts ReaderOption[E, ReaderOption[E, A]] to ReaderOption[E, A].
//
// Example:
//
// nested := readeroption.Of[Config](readeroption.Of[Config](42))
// flattened := readeroption.Flatten(nested)
func Flatten[E, A any](mma ReaderOption[E, ReaderOption[E, A]]) ReaderOption[E, A] {
return MonadChain(mma, function.Identity[ReaderOption[E, A]])
}
// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s
// `contramap`).
//
// This allows you to transform the environment before passing it to a computation.
//
// Example:
//
// type GlobalConfig struct { DB DBConfig }
// type DBConfig struct { Host string }
//
// // A computation that needs DBConfig
// query := func(cfg DBConfig) option.Option[User] { ... }
//
// // Transform GlobalConfig to DBConfig
// result := readeroption.Local(func(g GlobalConfig) DBConfig { return g.DB })(
// readeroption.Asks(query),
// )
func Local[A, R2, R1 any](f func(R2) R1) func(ReaderOption[R1, A]) ReaderOption[R2, A] {
return reader.Local[Option[A]](f)
}
// Read applies a context to a reader to obtain its value.
// This executes the ReaderOption computation with the given environment.
//
// Example:
//
// ro := readeroption.Of[Config](42)
// result := readeroption.Read[int](myConfig)(ro) // Returns option.Some(42)
func Read[A, E any](e E) func(ReaderOption[E, A]) Option[A] {
return reader.Read[E, Option[A]](e)
}
// MonadFlap applies a value to a function wrapped in a ReaderOption.
// This is the reverse of MonadAp.
func MonadFlap[E, A, B any](fab ReaderOption[E, func(A) B], a A) ReaderOption[E, B] {
return functor.MonadFlap(MonadMap[E, func(A) B, B], fab, a)
}
// Flap returns a function that applies a value to a function wrapped in a ReaderOption.
// This is the curried version of MonadFlap.
func Flap[E, B, A any](a A) Operator[E, func(A) B, B] {
return functor.Flap(Map[E, func(A) B, B], a)
}

View File

@@ -0,0 +1,248 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
"fmt"
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
type MyContext string
const defaultContext MyContext = "default"
func TestMap(t *testing.T) {
g := F.Pipe1(
Of[MyContext](1),
Map[MyContext](utils.Double),
)
assert.Equal(t, O.Of(2), g(defaultContext))
}
func TestAp(t *testing.T) {
g := F.Pipe1(
Of[MyContext](utils.Double),
Ap[int](Of[MyContext](1)),
)
assert.Equal(t, O.Of(2), g(defaultContext))
}
func TestFlatten(t *testing.T) {
g := F.Pipe1(
Of[MyContext](Of[MyContext]("a")),
Flatten[MyContext, string],
)
assert.Equal(t, O.Of("a"), g(defaultContext))
}
func TestFromOption(t *testing.T) {
// Test with Some
opt1 := O.Of(42)
ro1 := FromOption[MyContext](opt1)
assert.Equal(t, O.Of(42), ro1(defaultContext))
// Test with None
opt2 := O.None[int]()
ro2 := FromOption[MyContext](opt2)
assert.Equal(t, O.None[int](), ro2(defaultContext))
}
func TestSome(t *testing.T) {
ro := Some[MyContext](42)
assert.Equal(t, O.Of(42), ro(defaultContext))
}
func TestFromReader(t *testing.T) {
reader := func(ctx MyContext) int {
return 42
}
ro := FromReader(reader)
assert.Equal(t, O.Of(42), ro(defaultContext))
}
func TestOf(t *testing.T) {
ro := Of[MyContext](42)
assert.Equal(t, O.Of(42), ro(defaultContext))
}
func TestNone(t *testing.T) {
ro := None[MyContext, int]()
assert.Equal(t, O.None[int](), ro(defaultContext))
}
func TestChain(t *testing.T) {
double := func(x int) ReaderOption[MyContext, int] {
return Of[MyContext](x * 2)
}
g := F.Pipe1(
Of[MyContext](21),
Chain(double),
)
assert.Equal(t, O.Of(42), g(defaultContext))
// Test with None
g2 := F.Pipe1(
None[MyContext, int](),
Chain(double),
)
assert.Equal(t, O.None[int](), g2(defaultContext))
}
func TestFromPredicate(t *testing.T) {
isPositive := FromPredicate[MyContext](func(x int) bool {
return x > 0
})
// Test with positive number
g1 := F.Pipe1(
Of[MyContext](42),
Chain(isPositive),
)
assert.Equal(t, O.Of(42), g1(defaultContext))
// Test with negative number
g2 := F.Pipe1(
Of[MyContext](-5),
Chain(isPositive),
)
assert.Equal(t, O.None[int](), g2(defaultContext))
}
func TestFold(t *testing.T) {
onNone := func() Reader[MyContext, string] {
return func(ctx MyContext) string {
return "none"
}
}
onSome := func(x int) Reader[MyContext, string] {
return func(ctx MyContext) string {
return fmt.Sprintf("%d", x)
}
}
// Test with Some
g1 := Fold(onNone, onSome)(Of[MyContext](42))
assert.Equal(t, "42", g1(defaultContext))
// Test with None
g2 := Fold(onNone, onSome)(None[MyContext, int]())
assert.Equal(t, "none", g2(defaultContext))
}
func TestGetOrElse(t *testing.T) {
defaultValue := func() Reader[MyContext, int] {
return func(ctx MyContext) int {
return 0
}
}
// Test with Some
g1 := GetOrElse(defaultValue)(Of[MyContext](42))
assert.Equal(t, 42, g1(defaultContext))
// Test with None
g2 := GetOrElse(defaultValue)(None[MyContext, int]())
assert.Equal(t, 0, g2(defaultContext))
}
func TestAsk(t *testing.T) {
ro := Ask[MyContext, any]()
result := ro(defaultContext)
assert.Equal(t, O.Of(defaultContext), result)
}
func TestAsks(t *testing.T) {
reader := func(ctx MyContext) string {
return string(ctx)
}
ro := Asks(reader)
result := ro(defaultContext)
assert.Equal(t, O.Of("default"), result)
}
func TestChainOptionK(t *testing.T) {
parsePositive := func(x int) O.Option[int] {
if x > 0 {
return O.Of(x)
}
return O.None[int]()
}
// Test with positive number
g1 := F.Pipe1(
Of[MyContext](42),
ChainOptionK[MyContext](parsePositive),
)
assert.Equal(t, O.Of(42), g1(defaultContext))
// Test with negative number
g2 := F.Pipe1(
Of[MyContext](-5),
ChainOptionK[MyContext](parsePositive),
)
assert.Equal(t, O.None[int](), g2(defaultContext))
}
func TestLocal(t *testing.T) {
type GlobalContext struct {
Value string
}
// A computation that needs a string context
ro := Asks(func(s string) string {
return "Hello, " + s
})
// Transform GlobalContext to string
transformed := Local[string](func(g GlobalContext) string {
return g.Value
})(ro)
result := transformed(GlobalContext{Value: "World"})
assert.Equal(t, O.Of("Hello, World"), result)
}
func TestRead(t *testing.T) {
ro := Of[MyContext](42)
result := Read[int](defaultContext)(ro)
assert.Equal(t, O.Of(42), result)
}
func TestFlap(t *testing.T) {
addFunc := func(x int) int {
return x + 10
}
g := F.Pipe1(
Of[MyContext](addFunc),
Flap[MyContext, int](32),
)
assert.Equal(t, O.Of(42), g(defaultContext))
}

127
v2/readeroption/sequence.go Normal file
View File

@@ -0,0 +1,127 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
G "github.com/IBM/fp-go/v2/readeroption/generic"
T "github.com/IBM/fp-go/v2/tuple"
)
// SequenceT functions convert multiple ReaderOption values into a single ReaderOption containing a tuple.
// If any input is None, the entire result is None.
// Otherwise, returns Some containing a tuple of all the unwrapped values.
//
// These functions are useful for combining multiple independent ReaderOption computations
// where you need to preserve the individual types of each result.
// SequenceT1 converts a single ReaderOption into a ReaderOption of a 1-tuple.
// This is mainly useful for consistency with the other SequenceT functions.
//
// Example:
//
// type Config struct { ... }
//
// user := readeroption.Of[Config](User{Name: "Alice"})
// result := readeroption.SequenceT1(user)
// // result(config) returns option.Some(tuple.MakeTuple1(User{Name: "Alice"}))
func SequenceT1[E, A any](a ReaderOption[E, A]) ReaderOption[E, T.Tuple1[A]] {
return G.SequenceT1[
ReaderOption[E, A],
ReaderOption[E, T.Tuple1[A]],
](a)
}
// SequenceT2 combines two ReaderOption values into a ReaderOption of a 2-tuple.
// If either input is None, the result is None.
//
// Example:
//
// type Config struct { ... }
//
// user := readeroption.Of[Config](User{Name: "Alice"})
// count := readeroption.Of[Config](42)
//
// result := readeroption.SequenceT2(user, count)
// // result(config) returns option.Some(tuple.MakeTuple2(User{Name: "Alice"}, 42))
//
// noneUser := readeroption.None[Config, User]()
// result2 := readeroption.SequenceT2(noneUser, count)
// // result2(config) returns option.None[tuple.Tuple2[User, int]]()
func SequenceT2[E, A, B any](
a ReaderOption[E, A],
b ReaderOption[E, B],
) ReaderOption[E, T.Tuple2[A, B]] {
return G.SequenceT2[
ReaderOption[E, A],
ReaderOption[E, B],
ReaderOption[E, T.Tuple2[A, B]],
](a, b)
}
// SequenceT3 combines three ReaderOption values into a ReaderOption of a 3-tuple.
// If any input is None, the result is None.
//
// Example:
//
// type Config struct { ... }
//
// user := readeroption.Of[Config](User{Name: "Alice"})
// count := readeroption.Of[Config](42)
// active := readeroption.Of[Config](true)
//
// result := readeroption.SequenceT3(user, count, active)
// // result(config) returns option.Some(tuple.MakeTuple3(User{Name: "Alice"}, 42, true))
func SequenceT3[E, A, B, C any](
a ReaderOption[E, A],
b ReaderOption[E, B],
c ReaderOption[E, C],
) ReaderOption[E, T.Tuple3[A, B, C]] {
return G.SequenceT3[
ReaderOption[E, A],
ReaderOption[E, B],
ReaderOption[E, C],
ReaderOption[E, T.Tuple3[A, B, C]],
](a, b, c)
}
// SequenceT4 combines four ReaderOption values into a ReaderOption of a 4-tuple.
// If any input is None, the result is None.
//
// Example:
//
// type Config struct { ... }
//
// user := readeroption.Of[Config](User{Name: "Alice"})
// count := readeroption.Of[Config](42)
// active := readeroption.Of[Config](true)
// score := readeroption.Of[Config](95.5)
//
// result := readeroption.SequenceT4(user, count, active, score)
// // result(config) returns option.Some(tuple.MakeTuple4(User{Name: "Alice"}, 42, true, 95.5))
func SequenceT4[E, A, B, C, D any](
a ReaderOption[E, A],
b ReaderOption[E, B],
c ReaderOption[E, C],
d ReaderOption[E, D],
) ReaderOption[E, T.Tuple4[A, B, C, D]] {
return G.SequenceT4[
ReaderOption[E, A],
ReaderOption[E, B],
ReaderOption[E, C],
ReaderOption[E, D],
ReaderOption[E, T.Tuple4[A, B, C, D]],
](a, b, c, d)
}

View File

@@ -0,0 +1,87 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readeroption
import (
"testing"
O "github.com/IBM/fp-go/v2/option"
T "github.com/IBM/fp-go/v2/tuple"
"github.com/stretchr/testify/assert"
)
func TestSequenceT1(t *testing.T) {
t1 := Of[MyContext]("s1")
e1 := None[MyContext, string]()
res1 := SequenceT1(t1)
assert.Equal(t, O.Of(T.MakeTuple1("s1")), res1(defaultContext))
res2 := SequenceT1(e1)
assert.Equal(t, O.None[T.Tuple1[string]](), res2(defaultContext))
}
func TestSequenceT2(t *testing.T) {
t1 := Of[MyContext]("s1")
e1 := None[MyContext, string]()
t2 := Of[MyContext](2)
e2 := None[MyContext, int]()
res1 := SequenceT2(t1, t2)
assert.Equal(t, O.Of(T.MakeTuple2("s1", 2)), res1(defaultContext))
res2 := SequenceT2(e1, t2)
assert.Equal(t, O.None[T.Tuple2[string, int]](), res2(defaultContext))
res3 := SequenceT2(t1, e2)
assert.Equal(t, O.None[T.Tuple2[string, int]](), res3(defaultContext))
}
func TestSequenceT3(t *testing.T) {
t1 := Of[MyContext]("s1")
e1 := None[MyContext, string]()
t2 := Of[MyContext](2)
e2 := None[MyContext, int]()
t3 := Of[MyContext](true)
e3 := None[MyContext, bool]()
res1 := SequenceT3(t1, t2, t3)
assert.Equal(t, O.Of(T.MakeTuple3("s1", 2, true)), res1(defaultContext))
res2 := SequenceT3(e1, t2, t3)
assert.Equal(t, O.None[T.Tuple3[string, int, bool]](), res2(defaultContext))
res3 := SequenceT3(t1, e2, t3)
assert.Equal(t, O.None[T.Tuple3[string, int, bool]](), res3(defaultContext))
res4 := SequenceT3(t1, t2, e3)
assert.Equal(t, O.None[T.Tuple3[string, int, bool]](), res4(defaultContext))
}
func TestSequenceT4(t *testing.T) {
t1 := Of[MyContext]("s1")
t2 := Of[MyContext](2)
t3 := Of[MyContext](true)
t4 := Of[MyContext](1.0)
res := SequenceT4(t1, t2, t3, t4)
assert.Equal(t, O.Of(T.MakeTuple4("s1", 2, true, 1.0)), res(defaultContext))
}

92
v2/readeroption/types.go Normal file
View File

@@ -0,0 +1,92 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package readeroption provides a monad transformer that combines the Reader and Option monads.
//
// ReaderOption[R, A] represents a computation that:
// - Depends on a shared environment of type R (Reader monad)
// - May fail to produce a value of type A (Option monad)
//
// This is useful for computations that need access to configuration, context, or dependencies
// while also being able to represent the absence of a value without using errors.
//
// The ReaderOption monad is defined as: Reader[R, Option[A]]
//
// Key operations:
// - Of: Wraps a value in a ReaderOption
// - None: Creates a ReaderOption representing no value
// - Map: Transforms the value inside a ReaderOption
// - Chain: Sequences ReaderOption computations
// - Ask/Asks: Accesses the environment
//
// Example:
//
// type Config struct {
// DatabaseURL string
// Timeout int
// }
//
// // A computation that may or may not find a user
// func findUser(id int) readeroption.ReaderOption[Config, User] {
// return readeroption.Asks(func(cfg Config) option.Option[User] {
// // Use cfg.DatabaseURL to query database
// // Return Some(user) if found, None() if not found
// })
// }
//
// // Chain multiple operations
// result := F.Pipe2(
// findUser(123),
// readeroption.Chain(func(user User) readeroption.ReaderOption[Config, Profile] {
// return loadProfile(user.ProfileID)
// }),
// readeroption.Map(func(profile Profile) string {
// return profile.DisplayName
// }),
// )
//
// // Execute with config
// config := Config{DatabaseURL: "localhost:5432", Timeout: 30}
// displayName := result(config) // Returns Option[string]
package readeroption
import (
"github.com/IBM/fp-go/v2/lazy"
"github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader"
)
type (
// Lazy represents a deferred computation
Lazy[A any] = lazy.Lazy[A]
// Option represents an optional value
Option[A any] = option.Option[A]
// Reader represents a computation that depends on an environment R and produces a value A
Reader[R, A any] = reader.Reader[R, A]
// ReaderOption represents a computation that depends on an environment R and may produce a value A.
// It combines the Reader monad (for dependency injection) with the Option monad (for optional values).
ReaderOption[R, A any] = Reader[R, Option[A]]
// Kleisli represents a function that takes a value A and returns a ReaderOption[R, B].
// This is the type of functions used with Chain/Bind operations.
Kleisli[R, A, B any] = Reader[A, ReaderOption[R, B]]
// Operator represents a function that transforms one ReaderOption into another.
// This is commonly used for lifting functions into the ReaderOption context.
Operator[R, A, B any] = Reader[ReaderOption[R, A], ReaderOption[R, B]]
)

View File

@@ -41,6 +41,9 @@ var (
// Includes returns a predicate that tests for the existence of the search string
Includes = F.Curry2(F.Swap(strings.Contains))
// HasPrefix returns a predicate that checks if the prefis is included in the string
HasPrefix = F.Curry2(F.Swap(strings.HasPrefix))
)
func Eq(left string, right string) bool {

View File

@@ -48,3 +48,9 @@ func TestIncludes(t *testing.T) {
assert.False(t, Includes("bab")("a"))
assert.False(t, Includes("b")("a"))
}
func TestHasPrefix(t *testing.T) {
assert.True(t, HasPrefix("prefix")("prefixbab"))
assert.False(t, HasPrefix("bab")("a"))
assert.False(t, HasPrefix("b")("a"))
}