1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-12-17 23:37:41 +02:00
Files
fp-go/v2/idiomatic/context/readerresult/doc.go
Dr. Carsten Leue 4ebfcadabe fix: add better tests
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-12-16 14:03:01 +01:00

208 lines
8.3 KiB
Go

// 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 readerresult provides a ReaderResult monad specialized for context.Context.
//
// A ReaderResult[A] represents an effectful computation that:
// - Takes a context.Context as input
// - May fail with an error (Result aspect, which is Either[error, A])
// - Returns a value of type A on success
//
// The type is defined as: ReaderResult[A any] = func(context.Context) (A, error)
//
// This is equivalent to Reader[context.Context, Result[A]] or Reader[context.Context, Either[error, A]],
// but specialized to always use context.Context as the environment type.
//
// # Effectful Computations with Context
//
// ReaderResult is particularly well-suited for representing effectful computations in Go. An effectful
// computation is one that:
//
// - Performs side effects (I/O, network calls, database operations, etc.)
// - May fail with an error
// - Requires contextual information (cancellation, deadlines, request-scoped values)
//
// By using context.Context as the fixed environment type, ReaderResult[A] provides:
//
// 1. Cancellation propagation - operations can be cancelled via context
// 2. Deadline/timeout handling - operations respect context deadlines
// 3. Request-scoped values - access to request metadata, trace IDs, etc.
// 4. Functional composition - chain effectful operations while maintaining context
// 5. Error handling - explicit error propagation through the Result type
//
// This pattern is idiomatic in Go, where functions performing I/O conventionally accept
// context.Context as their first parameter: func(ctx context.Context, ...) (Result, error).
// ReaderResult preserves this convention while enabling functional composition.
//
// Example of an effectful computation:
//
// // An effectful operation that queries a database
// func fetchUser(ctx context.Context, id int) (User, error) {
// // ctx provides cancellation, deadlines, and request context
// row := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", id)
// var user User
// err := row.Scan(&user.ID, &user.Name)
// return user, err
// }
//
// // Lift into ReaderResult for functional composition
// getUser := readerresult.Curry1(fetchUser)
//
// // Compose multiple effectful operations
// pipeline := F.Pipe2(
// getUser(42), // ReaderResult[User]
// readerresult.Chain(func(user User) readerresult.ReaderResult[[]Post] {
// return getPosts(user.ID) // Another effectful operation
// }),
// )
//
// // Execute with a context (e.g., from an HTTP request)
// ctx := r.Context() // HTTP request context
// posts, err := pipeline(ctx)
//
// # Use Cases
//
// ReaderResult is particularly useful for:
//
// 1. Effectful computations with context - operations that perform I/O and need cancellation/deadlines
// 2. Functional error handling - compose operations that depend on context and may error
// 3. Testing - easily mock context-dependent operations
// 4. HTTP handlers - chain request processing operations with proper context propagation
//
// # Composition
//
// ReaderResult provides several ways to compose computations:
//
// 1. Map - transform successful values
// 2. Chain (FlatMap) - sequence dependent operations
// 3. Ap - combine independent computations
// 4. Do-notation - imperative-style composition with Bind
//
// # Do-Notation Example
//
// type State struct {
// User User
// Posts []Post
// }
//
// result := F.Pipe2(
// readerresult.Do(State{}),
// readerresult.Bind(
// func(user User) func(State) State {
// return func(s State) State { s.User = user; return s }
// },
// func(s State) readerresult.ReaderResult[User] {
// return getUser(42)
// },
// ),
// readerresult.Bind(
// func(posts []Post) func(State) State {
// return func(s State) State { s.Posts = posts; return s }
// },
// func(s State) readerresult.ReaderResult[[]Post] {
// return getPosts(s.User.ID)
// },
// ),
// )
//
// # Currying Functions with Context
//
// The Curry functions enable partial application of function parameters while deferring
// the context.Context parameter until execution time.
//
// When you curry a function like func(context.Context, T1, T2) (A, error), the context.Context
// becomes the last argument to be applied, even though it appears first in the original function
// signature. This is intentional and follows Go's context-first convention while enabling
// functional composition patterns.
//
// Why context.Context is the last curried argument:
//
// - In Go, context conventionally comes first: func(ctx context.Context, params...) (Result, error)
// - In curried form: Curry2(f)(param1)(param2) returns ReaderResult[A]
// - The ReaderResult is then applied to ctx: Curry2(f)(param1)(param2)(ctx)
// - This allows partial application of business parameters before providing the context
//
// Example with database operations:
//
// // Database operations following Go conventions (context first)
// func fetchUser(ctx context.Context, db *sql.DB, id int) (User, error) {
// row := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", id)
// var user User
// err := row.Scan(&user.ID, &user.Name)
// return user, err
// }
//
// func updateUser(ctx context.Context, db *sql.DB, id int, name string) (User, error) {
// _, err := db.ExecContext(ctx, "UPDATE users SET name = ? WHERE id = ?", name, id)
// if err != nil {
// return User{}, err
// }
// return fetchUser(ctx, db, id)
// }
//
// // Curry these into composable operations
// getUser := readerresult.Curry2(fetchUser)
// updateUserName := readerresult.Curry3(updateUser)
//
// // Compose operations with partial application
// pipeline := F.Pipe2(
// getUser(db)(42), // ReaderResult[User] - db and id applied, waiting for ctx
// readerresult.Chain(func(user User) readerresult.ReaderResult[User] {
// newName := user.Name + " (updated)"
// return updateUserName(db)(user.ID)(newName) // Waiting for ctx
// }),
// )
//
// // Execute by providing the context
// ctx := context.Background()
// updatedUser, err := pipeline(ctx)
//
// The key insight is that currying creates a chain where:
// 1. Business parameters are applied first: getUser(db)(42)
// 2. This returns a ReaderResult[User] that waits for the context
// 3. Multiple operations can be composed before providing the context
// 4. Finally, the context is provided to execute everything: pipeline(ctx)
//
// This pattern is particularly useful for:
// - Creating reusable operation pipelines independent of specific contexts
// - Testing with different contexts (with timeouts, cancellation, etc.)
// - Composing operations that share the same context
// - Deferring context creation until execution time
//
// # Error Handling
//
// ReaderResult provides several functions for error handling:
//
// - Left/Right - create failed/successful values
// - GetOrElse - provide a default value for errors
// - OrElse - recover from errors with an alternative computation
// - Fold - handle both success and failure cases
// - ChainEitherK - lift result.Result computations into ReaderResult
//
// # Relationship to Other Monads
//
// ReaderResult is related to several other monads in this library:
//
// - Reader[context.Context, A] - ReaderResult without error handling
// - Result[A] (Either[error, A]) - error handling without context dependency
// - IOResult[A] - similar to ReaderResult but without explicit context parameter
// - ReaderIOResult[R, A] - generic version that allows custom environment type R
//
// # Performance Note
//
// ReaderResult is a zero-cost abstraction - it compiles to a simple function type
// with no runtime overhead beyond the underlying computation.
package readerresult