Combinators¶
Functions for composing and transforming effects.
All combinators are curried — they return a function that takes an effect,
making them composable with pipe.
pyfect.combinators.map(f)
¶
Transform the success value of an effect.
Returns a function that takes an effect and returns a new effect with the success value transformed by f. The error and context types are preserved.
Example
Source code in src/pyfect/combinators.py
pyfect.combinators.flat_map(f)
¶
Chain effects together (monadic bind).
Returns a function that takes an effect and returns a new effect by applying f to the success value. Unlike map, f returns an Effect which is then flattened, avoiding nested effects.
This is useful for sequencing operations where each step depends on the result of the previous one.
Example
from pyfect import effect, pipe
def fetch_user(user_id: int) -> effect.Effect[str, str, None]:
return effect.succeed(f"User{user_id}")
# Chain effects where next depends on previous result
result = pipe(
effect.succeed(42),
effect.flat_map(lambda id: fetch_user(id))
)
effect.run_sync(result) # "User42"
# Multiple chaining
result = pipe(
effect.succeed(1),
effect.flat_map(lambda x: effect.succeed(x + 1)),
effect.flat_map(lambda x: effect.succeed(x * 2))
)
effect.run_sync(result) # 4
Source code in src/pyfect/combinators.py
pyfect.combinators.map_error(f)
¶
Transform the error type of an effect.
Returns a function that takes an effect and returns a new effect with the error type transformed by f. The success value and context types are preserved.
This is the counterpart to map - while map transforms success values, map_error transforms error values.
Example
from pyfect import effect, pipe
# Transform error messages
result = pipe(
effect.fail("file not found"),
effect.map_error(lambda msg: f"Error: {msg}")
)
# Will fail with "Error: file not found"
# Convert string errors to custom error types
class MyError(Exception):
def __init__(self, msg: str):
self.message = msg
result = pipe(
effect.fail("oops"),
effect.map_error(lambda msg: MyError(msg))
)
Source code in src/pyfect/combinators.py
pyfect.combinators.tap(f)
¶
Inspect the success value without modifying it.
Returns a function that takes an effect and returns a new effect. The function f is called with the success value and returns an effect that is executed for its side effects. The original value is passed through.
The tap function may have a different error type E2; any error it produces merges with the outer E type. The context type R must match. The success type B is discarded.
Works with both sync and async effects - the runtime handles it uniformly.
Example
Source code in src/pyfect/combinators.py
pyfect.combinators.tap_error(f)
¶
Inspect the error value without modifying it.
Returns a function that takes an effect and returns a new effect. The function f is called with the error value and returns an effect that is executed for its side effects. The original error is passed through.
The tap_error function may have a different error type E2; any error it produces merges with the outer E type. The context type R must match. The success type B is discarded.
Example
Source code in src/pyfect/combinators.py
pyfect.combinators.as_(value)
¶
Replace the success value with a constant value.
Returns a function that takes an effect and returns a new effect that ignores the original success value and replaces it with the provided constant. The error and context types are preserved.
This is equivalent to map(lambda _: value) but more explicit.
Example
Source code in src/pyfect/combinators.py
pyfect.combinators.ignore()
¶
Ignore both success and failure, always succeeding with None.
This runs the effect for its side effects but discards the result, whether it succeeds or fails. The returned effect can never fail (error type is Never).
Useful when you only care about the side effects of an effect and don't need to handle or process its outcome.
Example
from pyfect import effect, pipe
# Ignore success
result = pipe(
effect.succeed(42),
effect.ignore()
)
effect.run_sync(result) # None
# Ignore failure too
result = pipe(
effect.fail("error"),
effect.ignore()
)
effect.run_sync(result) # None (no error raised!)
# Useful for fire-and-forget operations
result = pipe(
effect.try_sync(lambda: risky_operation()),
effect.ignore() # Don't care if it succeeds or fails
)