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:
@@ -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}}]
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
87
v2/internal/fromoption/option.go
Normal file
87
v2/internal/fromoption/option.go
Normal 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))
|
||||
// }
|
||||
28
v2/internal/fromoption/types.go
Normal file
28
v2/internal/fromoption/types.go
Normal 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
|
||||
}
|
||||
)
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
88
v2/readeroption/array.go
Normal 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)
|
||||
}
|
||||
107
v2/readeroption/array_test.go
Normal file
107
v2/readeroption/array_test.go
Normal 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
303
v2/readeroption/bind.go
Normal 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)
|
||||
}
|
||||
99
v2/readeroption/bind_test.go
Normal file
99
v2/readeroption/bind_test.go
Normal 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
113
v2/readeroption/curry.go
Normal 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)
|
||||
}
|
||||
164
v2/readeroption/curry_test.go
Normal file
164
v2/readeroption/curry_test.go
Normal 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
98
v2/readeroption/from.go
Normal 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)
|
||||
}
|
||||
114
v2/readeroption/from_test.go
Normal file
114
v2/readeroption/from_test.go
Normal 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
|
||||
60
v2/readeroption/generic/array.go
Normal file
60
v2/readeroption/generic/array.go
Normal 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])
|
||||
}
|
||||
184
v2/readeroption/generic/bind.go
Normal file
184
v2/readeroption/generic/bind.go
Normal 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,
|
||||
)
|
||||
}
|
||||
52
v2/readeroption/generic/curry.go
Normal file
52
v2/readeroption/generic/curry.go
Normal 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))
|
||||
}
|
||||
40
v2/readeroption/generic/from.go
Normal file
40
v2/readeroption/generic/from.go
Normal 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))
|
||||
}
|
||||
129
v2/readeroption/generic/reader.go
Normal file
129
v2/readeroption/generic/reader.go
Normal 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)
|
||||
}
|
||||
80
v2/readeroption/generic/sequence.go
Normal file
80
v2/readeroption/generic/sequence.go
Normal 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
292
v2/readeroption/reader.go
Normal 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)
|
||||
}
|
||||
248
v2/readeroption/reader_test.go
Normal file
248
v2/readeroption/reader_test.go
Normal 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
127
v2/readeroption/sequence.go
Normal 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)
|
||||
}
|
||||
87
v2/readeroption/sequence_test.go
Normal file
87
v2/readeroption/sequence_test.go
Normal 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
92
v2/readeroption/types.go
Normal 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]]
|
||||
)
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user