From b3bd5e9ad3029405bf1305e64f5b42e6867c49fb Mon Sep 17 00:00:00 2001 From: "Dr. Carsten Leue" Date: Thu, 6 Nov 2025 16:18:15 +0100 Subject: [PATCH] fix: bind docs Signed-off-by: Dr. Carsten Leue --- v2/array/generic/bind.go | 46 ++++++++++++++++++++- v2/context/readereither/bind.go | 53 +++++++++++++++++++++++- v2/context/readerioeither/bind.go | 54 ++++++++++++++++++++++++- v2/identity/bind.go | 45 ++++++++++++++++++++- v2/ioeither/bind.go | 49 +++++++++++++++++++++- v2/iooption/bind.go | 45 ++++++++++++++++++++- v2/iterator/stateless/bind.go | 46 ++++++++++++++++++++- v2/iterator/stateless/generic/bind.go | 46 ++++++++++++++++++++- v2/lazy/bind.go | 45 ++++++++++++++++++++- v2/readereither/bind.go | 58 ++++++++++++++++++++++++++- v2/readereither/generic/bind.go | 58 ++++++++++++++++++++++++++- v2/readerio/bind.go | 58 ++++++++++++++++++++++++++- v2/readerioeither/bind.go | 58 ++++++++++++++++++++++++++- v2/readerioeither/generic/bind.go | 58 ++++++++++++++++++++++++++- v2/record/bind.go | 46 ++++++++++++++++++++- v2/record/generic/bind.go | 50 ++++++++++++++++++++++- 16 files changed, 783 insertions(+), 32 deletions(-) diff --git a/v2/array/generic/bind.go b/v2/array/generic/bind.go index 1d928f6..3715cc6 100644 --- a/v2/array/generic/bind.go +++ b/v2/array/generic/bind.go @@ -21,14 +21,56 @@ import ( F "github.com/IBM/fp-go/v2/internal/functor" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 { +// X int +// Y int +// } +// result := generic.Do[[]State, State](State{}) func Do[GS ~[]S, 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] +// 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. +// For arrays, this produces the cartesian product where later steps can use values from earlier steps. +// +// 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 { +// X int +// Y int +// } +// +// result := F.Pipe2( +// generic.Do[[]State, State](State{}), +// generic.Bind[[]State, []State, []int, State, State, int]( +// func(x int) func(State) State { +// return func(s State) State { s.X = x; return s } +// }, +// func(s State) []int { +// return []int{1, 2, 3} +// }, +// ), +// generic.Bind[[]State, []State, []int, State, State, int]( +// func(y int) func(State) State { +// return func(s State) State { s.Y = y; return s } +// }, +// func(s State) []int { +// // This can access s.X from the previous step +// return []int{s.X * 10, s.X * 20} +// }, +// ), +// ) // Produces: {1,10}, {1,20}, {2,20}, {2,40}, {3,30}, {3,60} func Bind[GS1 ~[]S1, GS2 ~[]S2, GT ~[]T, S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) GT, diff --git a/v2/context/readereither/bind.go b/v2/context/readereither/bind.go index a3e9518..1fc1862 100644 --- a/v2/context/readereither/bind.go +++ b/v2/context/readereither/bind.go @@ -21,14 +21,63 @@ import ( G "github.com/IBM/fp-go/v2/readereither/generic" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 { +// UserID string +// TenantID string +// } +// result := readereither.Do(State{}) func Do[S any]( empty S, ) ReaderEither[S] { return G.Do[ReaderEither[S], context.Context, error, S](empty) } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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 context.Context from the 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 { +// UserID string +// TenantID string +// } +// +// result := F.Pipe2( +// readereither.Do(State{}), +// readereither.Bind( +// func(uid string) func(State) State { +// return func(s State) State { s.UserID = uid; return s } +// }, +// func(s State) readereither.ReaderEither[string] { +// return func(ctx context.Context) either.Either[error, string] { +// if uid, ok := ctx.Value("userID").(string); ok { +// return either.Right[error](uid) +// } +// return either.Left[string](errors.New("no userID")) +// } +// }, +// ), +// readereither.Bind( +// func(tid string) func(State) State { +// return func(s State) State { s.TenantID = tid; return s } +// }, +// func(s State) readereither.ReaderEither[string] { +// // This can access s.UserID from the previous step +// return func(ctx context.Context) either.Either[error, string] { +// return either.Right[error]("tenant-" + s.UserID) +// } +// }, +// ), +// ) func Bind[S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) ReaderEither[T], diff --git a/v2/context/readerioeither/bind.go b/v2/context/readerioeither/bind.go index 94fecf8..b000774 100644 --- a/v2/context/readerioeither/bind.go +++ b/v2/context/readerioeither/bind.go @@ -21,14 +21,64 @@ import ( "github.com/IBM/fp-go/v2/internal/functor" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 +// } +// result := readerioeither.Do(State{}) func Do[S any]( empty S, ) ReaderIOEither[S] { return Of(empty) } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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 context.Context from the 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 +// } +// +// result := F.Pipe2( +// readerioeither.Do(State{}), +// readerioeither.Bind( +// func(user User) func(State) State { +// return func(s State) State { s.User = user; return s } +// }, +// func(s State) readerioeither.ReaderIOEither[User] { +// return func(ctx context.Context) ioeither.IOEither[error, User] { +// return ioeither.TryCatch(func() (User, error) { +// return fetchUser(ctx) +// }) +// } +// }, +// ), +// readerioeither.Bind( +// func(cfg Config) func(State) State { +// return func(s State) State { s.Config = cfg; return s } +// }, +// func(s State) readerioeither.ReaderIOEither[Config] { +// // This can access s.User from the previous step +// return func(ctx context.Context) ioeither.IOEither[error, Config] { +// return ioeither.TryCatch(func() (Config, error) { +// return fetchConfigForUser(ctx, s.User.ID) +// }) +// } +// }, +// ), +// ) func Bind[S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) ReaderIOEither[T], diff --git a/v2/identity/bind.go b/v2/identity/bind.go index 86b84ca..cd08fc4 100644 --- a/v2/identity/bind.go +++ b/v2/identity/bind.go @@ -21,14 +21,55 @@ import ( F "github.com/IBM/fp-go/v2/internal/functor" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 { +// X int +// Y int +// } +// result := identity.Do(State{}) func Do[S any]( empty S, ) S { return empty } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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. +// +// 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 { +// X int +// Y int +// } +// +// result := F.Pipe2( +// identity.Do(State{}), +// identity.Bind( +// func(x int) func(State) State { +// return func(s State) State { s.X = x; return s } +// }, +// func(s State) int { +// return 42 +// }, +// ), +// identity.Bind( +// func(y int) func(State) State { +// return func(s State) State { s.Y = y; return s } +// }, +// func(s State) int { +// // This can access s.X from the previous step +// return s.X * 2 +// }, +// ), +// ) // State{X: 42, Y: 84} func Bind[S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) T, diff --git a/v2/ioeither/bind.go b/v2/ioeither/bind.go index d0ffed2..2b8e65b 100644 --- a/v2/ioeither/bind.go +++ b/v2/ioeither/bind.go @@ -21,14 +21,59 @@ import ( "github.com/IBM/fp-go/v2/internal/functor" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 +// Posts []Post +// } +// result := ioeither.Do[error](State{}) func Do[E, S any]( empty S, ) IOEither[E, S] { return Of[E](empty) } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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. +// +// 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 +// Posts []Post +// } +// +// result := F.Pipe2( +// ioeither.Do[error](State{}), +// ioeither.Bind( +// func(user User) func(State) State { +// return func(s State) State { s.User = user; return s } +// }, +// func(s State) ioeither.IOEither[error, User] { +// return ioeither.TryCatch(func() (User, error) { +// return fetchUser() +// }) +// }, +// ), +// ioeither.Bind( +// func(posts []Post) func(State) State { +// return func(s State) State { s.Posts = posts; return s } +// }, +// func(s State) ioeither.IOEither[error, []Post] { +// // This can access s.User from the previous step +// return ioeither.TryCatch(func() ([]Post, error) { +// return fetchPostsForUser(s.User.ID) +// }) +// }, +// ), +// ) func Bind[E, S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) IOEither[E, T], diff --git a/v2/iooption/bind.go b/v2/iooption/bind.go index 92547c2..d32acad 100644 --- a/v2/iooption/bind.go +++ b/v2/iooption/bind.go @@ -21,14 +21,55 @@ import ( "github.com/IBM/fp-go/v2/internal/functor" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 { +// Name string +// Age int +// } +// result := iooption.Do(State{}) func Do[S any]( empty S, ) IOOption[S] { return Of(empty) } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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. +// +// 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 { +// Name string +// Age int +// } +// +// result := F.Pipe2( +// iooption.Do(State{}), +// iooption.Bind( +// func(name string) func(State) State { +// return func(s State) State { s.Name = name; return s } +// }, +// func(s State) iooption.IOOption[string] { +// return iooption.FromIO(io.Of("Alice")) +// }, +// ), +// iooption.Bind( +// func(age int) func(State) State { +// return func(s State) State { s.Age = age; return s } +// }, +// func(s State) iooption.IOOption[int] { +// // This can access s.Name from the previous step +// return iooption.FromIO(io.Of(len(s.Name) * 10)) +// }, +// ), +// ) func Bind[S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) IOOption[T], diff --git a/v2/iterator/stateless/bind.go b/v2/iterator/stateless/bind.go index 93d4978..ab37488 100644 --- a/v2/iterator/stateless/bind.go +++ b/v2/iterator/stateless/bind.go @@ -19,14 +19,56 @@ import ( G "github.com/IBM/fp-go/v2/iterator/stateless/generic" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 { +// X int +// Y int +// } +// result := stateless.Do(State{}) func Do[S any]( empty S, ) Iterator[S] { return G.Do[Iterator[S]](empty) } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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. +// For iterators, this produces the cartesian product of all values. +// +// 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 { +// X int +// Y int +// } +// +// result := F.Pipe2( +// stateless.Do(State{}), +// stateless.Bind( +// func(x int) func(State) State { +// return func(s State) State { s.X = x; return s } +// }, +// func(s State) stateless.Iterator[int] { +// return stateless.Of(1, 2, 3) +// }, +// ), +// stateless.Bind( +// func(y int) func(State) State { +// return func(s State) State { s.Y = y; return s } +// }, +// func(s State) stateless.Iterator[int] { +// // This can access s.X from the previous step +// return stateless.Of(s.X * 10, s.X * 20) +// }, +// ), +// ) // Produces: {1,10}, {1,20}, {2,20}, {2,40}, {3,30}, {3,60} func Bind[S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) Iterator[T], diff --git a/v2/iterator/stateless/generic/bind.go b/v2/iterator/stateless/generic/bind.go index 27191ac..f6fce2a 100644 --- a/v2/iterator/stateless/generic/bind.go +++ b/v2/iterator/stateless/generic/bind.go @@ -23,14 +23,56 @@ import ( P "github.com/IBM/fp-go/v2/pair" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 { +// X int +// Y int +// } +// result := generic.Do[Iterator[State]](State{}) func Do[GS ~func() O.Option[P.Pair[GS, S]], 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] +// 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. +// For iterators, this produces the cartesian product where later steps can use values from earlier steps. +// +// 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 { +// X int +// Y int +// } +// +// result := F.Pipe2( +// generic.Do[Iterator[State]](State{}), +// generic.Bind[Iterator[State], Iterator[State], Iterator[int], State, State, int]( +// func(x int) func(State) State { +// return func(s State) State { s.X = x; return s } +// }, +// func(s State) Iterator[int] { +// return generic.Of[Iterator[int]](1, 2, 3) +// }, +// ), +// generic.Bind[Iterator[State], Iterator[State], Iterator[int], State, State, int]( +// func(y int) func(State) State { +// return func(s State) State { s.Y = y; return s } +// }, +// func(s State) Iterator[int] { +// // This can access s.X from the previous step +// return generic.Of[Iterator[int]](s.X * 10, s.X * 20) +// }, +// ), +// ) // Produces: {1,10}, {1,20}, {2,20}, {2,40}, {3,30}, {3,60} func Bind[GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS2, S2]], GA ~func() O.Option[P.Pair[GA, A]], S1, S2, A any]( setter func(A) func(S1) S2, f func(S1) GA, diff --git a/v2/lazy/bind.go b/v2/lazy/bind.go index 7b03893..79b4170 100644 --- a/v2/lazy/bind.go +++ b/v2/lazy/bind.go @@ -19,14 +19,55 @@ import ( "github.com/IBM/fp-go/v2/io" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 +// Data Data +// } +// result := lazy.Do(State{}) func Do[S any]( empty S, ) Lazy[S] { return io.Do(empty) } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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. +// +// 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 +// Data Data +// } +// +// result := F.Pipe2( +// lazy.Do(State{}), +// lazy.Bind( +// func(cfg Config) func(State) State { +// return func(s State) State { s.Config = cfg; return s } +// }, +// func(s State) lazy.Lazy[Config] { +// return lazy.MakeLazy(func() Config { return loadConfig() }) +// }, +// ), +// lazy.Bind( +// func(data Data) func(State) State { +// return func(s State) State { s.Data = data; return s } +// }, +// func(s State) lazy.Lazy[Data] { +// // This can access s.Config from the previous step +// return lazy.MakeLazy(func() Data { return loadData(s.Config) }) +// }, +// ), +// ) func Bind[S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) Lazy[T], diff --git a/v2/readereither/bind.go b/v2/readereither/bind.go index 11045fd..a94d070 100644 --- a/v2/readereither/bind.go +++ b/v2/readereither/bind.go @@ -19,14 +19,68 @@ import ( G "github.com/IBM/fp-go/v2/readereither/generic" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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, E, S any]( empty S, ) ReaderEither[R, E, S] { return G.Do[ReaderEither[R, E, S], R, E, S](empty) } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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.ReaderEither[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.ReaderEither[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, E, S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) ReaderEither[R, E, T], diff --git a/v2/readereither/generic/bind.go b/v2/readereither/generic/bind.go index d7467cc..48c2ad6 100644 --- a/v2/readereither/generic/bind.go +++ b/v2/readereither/generic/bind.go @@ -22,14 +22,68 @@ import ( F "github.com/IBM/fp-go/v2/internal/functor" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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) ET.Either[E, S], R, E, S any]( empty S, ) GS { return Of[GS, E, R, S](empty) } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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) ET.Either[E, S1], GS2 ~func(R) ET.Either[E, S2], GT ~func(R) ET.Either[E, T], R, E, S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) GT, diff --git a/v2/readerio/bind.go b/v2/readerio/bind.go index 232280f..6874345 100644 --- a/v2/readerio/bind.go +++ b/v2/readerio/bind.go @@ -21,14 +21,68 @@ import ( "github.com/IBM/fp-go/v2/internal/functor" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 { +// Host string +// Port int +// } +// type Config struct { +// DefaultHost string +// DefaultPort int +// } +// result := readerio.Do[Config](State{}) func Do[R, S any]( empty S, ) ReaderIO[R, S] { return Of[R](empty) } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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 { +// Host string +// Port int +// } +// type Config struct { +// DefaultHost string +// DefaultPort int +// } +// +// result := F.Pipe2( +// readerio.Do[Config](State{}), +// readerio.Bind( +// func(host string) func(State) State { +// return func(s State) State { s.Host = host; return s } +// }, +// func(s State) readerio.ReaderIO[Config, string] { +// return readerio.Asks(func(c Config) io.IO[string] { +// return io.Of(c.DefaultHost) +// }) +// }, +// ), +// readerio.Bind( +// func(port int) func(State) State { +// return func(s State) State { s.Port = port; return s } +// }, +// func(s State) readerio.ReaderIO[Config, int] { +// // This can access s.Host from the previous step +// return readerio.Asks(func(c Config) io.IO[int] { +// return io.Of(c.DefaultPort) +// }) +// }, +// ), +// ) func Bind[R, S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) ReaderIO[R, T], diff --git a/v2/readerioeither/bind.go b/v2/readerioeither/bind.go index 6170294..5ad02df 100644 --- a/v2/readerioeither/bind.go +++ b/v2/readerioeither/bind.go @@ -20,14 +20,68 @@ import ( G "github.com/IBM/fp-go/v2/readerioeither/generic" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 +// Posts []Post +// } +// type Env struct { +// UserRepo UserRepository +// PostRepo PostRepository +// } +// result := readerioeither.Do[Env, error](State{}) func Do[R, E, S any]( empty S, ) ReaderIOEither[R, E, S] { return G.Do[ReaderIOEither[R, E, S], IOE.IOEither[E, S], R, E, S](empty) } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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 +// Posts []Post +// } +// type Env struct { +// UserRepo UserRepository +// PostRepo PostRepository +// } +// +// result := F.Pipe2( +// readerioeither.Do[Env, error](State{}), +// readerioeither.Bind( +// func(user User) func(State) State { +// return func(s State) State { s.User = user; return s } +// }, +// func(s State) readerioeither.ReaderIOEither[Env, error, User] { +// return readerioeither.Asks(func(env Env) ioeither.IOEither[error, User] { +// return env.UserRepo.FindUser() +// }) +// }, +// ), +// readerioeither.Bind( +// func(posts []Post) func(State) State { +// return func(s State) State { s.Posts = posts; return s } +// }, +// func(s State) readerioeither.ReaderIOEither[Env, error, []Post] { +// // This can access s.User from the previous step +// return readerioeither.Asks(func(env Env) ioeither.IOEither[error, []Post] { +// return env.PostRepo.FindPostsByUser(s.User.ID) +// }) +// }, +// ), +// ) func Bind[R, E, S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) ReaderIOEither[R, E, T], diff --git a/v2/readerioeither/generic/bind.go b/v2/readerioeither/generic/bind.go index 0ed55c2..4014dae 100644 --- a/v2/readerioeither/generic/bind.go +++ b/v2/readerioeither/generic/bind.go @@ -22,14 +22,68 @@ import ( F "github.com/IBM/fp-go/v2/internal/functor" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 { +// UserRepo UserRepository +// ConfigRepo ConfigRepository +// } +// result := generic.Do[ReaderIOEither[Env, error, State], IOEither[error, State], Env, error, State](State{}) func Do[GRS ~func(R) GS, GS ~func() either.Either[E, S], R, E, S any]( empty S, ) GRS { return Of[GRS, GS, R, E, S](empty) } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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 { +// UserRepo UserRepository +// ConfigRepo ConfigRepository +// } +// +// result := F.Pipe2( +// generic.Do[ReaderIOEither[Env, error, State], IOEither[error, State], Env, error, State](State{}), +// generic.Bind[ReaderIOEither[Env, error, State], ReaderIOEither[Env, error, State], ReaderIOEither[Env, error, User], IOEither[error, State], IOEither[error, State], IOEither[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) ReaderIOEither[Env, error, User] { +// return func(env Env) ioeither.IOEither[error, User] { +// return env.UserRepo.FindUser() +// } +// }, +// ), +// generic.Bind[ReaderIOEither[Env, error, State], ReaderIOEither[Env, error, State], ReaderIOEither[Env, error, Config], IOEither[error, State], IOEither[error, State], IOEither[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) ReaderIOEither[Env, error, Config] { +// // This can access s.User from the previous step +// return func(env Env) ioeither.IOEither[error, Config] { +// return env.ConfigRepo.LoadConfigForUser(s.User.ID) +// } +// }, +// ), +// ) func Bind[GRS1 ~func(R) GS1, GRS2 ~func(R) GS2, GRT ~func(R) GT, GS1 ~func() either.Either[E, S1], GS2 ~func() either.Either[E, S2], GT ~func() either.Either[E, T], R, E, S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) GRT, diff --git a/v2/record/bind.go b/v2/record/bind.go index d8c6275..67149ad 100644 --- a/v2/record/bind.go +++ b/v2/record/bind.go @@ -20,12 +20,54 @@ import ( G "github.com/IBM/fp-go/v2/record/generic" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 { +// Name string +// Count int +// } +// result := record.Do[string, State]() func Do[K comparable, S any]() map[K]S { return G.Do[map[K]S, K, S]() } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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. +// For records, this merges values by key. +// +// 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 { +// Name string +// Count int +// } +// +// result := F.Pipe2( +// record.Do[string, State](), +// record.Bind(monoid.Record[string, State]())( +// func(name string) func(State) State { +// return func(s State) State { s.Name = name; return s } +// }, +// func(s State) map[string]string { +// return map[string]string{"a": "Alice", "b": "Bob"} +// }, +// ), +// record.Bind(monoid.Record[string, State]())( +// func(count int) func(State) State { +// return func(s State) State { s.Count = count; return s } +// }, +// func(s State) map[string]int { +// // This can access s.Name from the previous step +// return map[string]int{"a": len(s.Name), "b": len(s.Name) * 2} +// }, +// ), +// ) func Bind[S1, T any, K comparable, S2 any](m Mo.Monoid[map[K]S2]) func(setter func(T) func(S1) S2, f func(S1) map[K]T) func(map[K]S1) map[K]S2 { return G.Bind[map[K]S1, map[K]S2, map[K]T, K, S1, S2, T](m) } diff --git a/v2/record/generic/bind.go b/v2/record/generic/bind.go index 30e588b..4b8a933 100644 --- a/v2/record/generic/bind.go +++ b/v2/record/generic/bind.go @@ -22,12 +22,58 @@ import ( Mo "github.com/IBM/fp-go/v2/monoid" ) -// Bind creates an empty context of type [S] to be used with the [Bind] operation +// 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 { +// Name string +// Count int +// } +// result := generic.Do[map[string]State, string, State]() func Do[GS ~map[K]S, K comparable, S any]() GS { return Empty[GS, K, S]() } -// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +// 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. +// For records, this merges values by key where later steps can use values from earlier steps. +// +// 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 { +// Name string +// Count int +// } +// +// result := F.Pipe2( +// generic.Do[map[string]State, string, State](), +// generic.Bind[map[string]State, map[string]State, map[string]string, string, State, State, string]( +// monoid.Record[string, State](), +// )( +// func(name string) func(State) State { +// return func(s State) State { s.Name = name; return s } +// }, +// func(s State) map[string]string { +// return map[string]string{"a": "Alice", "b": "Bob"} +// }, +// ), +// generic.Bind[map[string]State, map[string]State, map[string]int, string, State, State, int]( +// monoid.Record[string, State](), +// )( +// func(count int) func(State) State { +// return func(s State) State { s.Count = count; return s } +// }, +// func(s State) map[string]int { +// // This can access s.Name from the previous step +// return map[string]int{"a": len(s.Name), "b": len(s.Name) * 2} +// }, +// ), +// ) func Bind[GS1 ~map[K]S1, GS2 ~map[K]S2, GT ~map[K]T, K comparable, S1, S2, T any](m Mo.Monoid[GS2]) func(setter func(T) func(S1) S2, f func(S1) GT) func(GS1) GS2 { c := Chain[GS1, GS2, K, S1, S2](m) return func(setter func(T) func(S1) S2, f func(S1) GT) func(GS1) GS2 {