Skip to content

Commit 2b72f7a

Browse files
authored
Merge pull request #28 from CajunSystems/claude/review-effect-monad-docs-01MUVnR8rmRYVYJBXbtMfn9X
2 parents fb2f696 + 1a45e7e commit 2b72f7a

File tree

5 files changed

+19
-74
lines changed

5 files changed

+19
-74
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3030
- **Documentation**: Complete guide with examples, best practices, and use cases
3131

3232
- **Effect Monad for Functional Actors**: Complete functional programming API for actor behaviors
33-
- **Stack-Safe**: Uses Trampoline for unbounded effect composition without stack overflow
33+
- **Stack-Safe**: Unbounded effect composition without stack overflow (chain thousands of operations)
3434
- **Type-Safe Error Handling**: `Effect<State, Error, Result>` with explicit error channel
3535
- **Composable Operations**: `map`, `flatMap`, `andThen`, `filter`, `recover`, `zip`, `parZip`
3636
- **Request-Response Pattern**: `Effect.ask(pid, message, timeout)` for actor communication

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -986,7 +986,7 @@ counter.tell(new Increment(5));
986986
#### Why Use Effects?
987987

988988
- **Composable**: Build complex behaviors from simple building blocks
989-
- **Stack-Safe**: Uses Trampoline pattern to prevent stack overflow on deep compositions
989+
- **Stack-Safe**: Prevents stack overflow on deep compositions (chain thousands of operations safely)
990990
- **🚀 Blocking is Safe**: Cajun runs on Java 21+ Virtual Threads - write normal blocking code without fear!
991991
- No `CompletableFuture` chains or async/await complexity
992992
- Database calls, HTTP requests, file I/O - just write them naturally

docs/effect_monad_api.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The Effect monad provides a composable, type-safe, **stack-safe** way to build a
66

77
## Key Features
88

9-
- **Stack-Safe** - Uses Trampoline pattern to prevent stack overflow on deep compositions
9+
- **Stack-Safe** - Prevents stack overflow on deep compositions (chain thousands of operations safely)
1010
- **Simplified Type Signature** - `Effect<State, Error, Result>` with Message type at match level
1111
- **Idiomatic Java naming** - Uses `.of()` instead of `.pure()`, familiar to Java developers
1212
- **Composable** - Build complex behaviors from simple building blocks
@@ -56,11 +56,10 @@ An `Effect` represents a **stack-safe** computation that:
5656
- May produce a **result** value
5757
- May perform **side effects** (logging, sending messages, etc.)
5858
- May **fail** with an **error** of type `Error` (typically `Throwable`)
59-
- Returns a `Trampoline<EffectResult<State, Result>>` for stack safety
6059

6160
**Key Changes from Previous Version:**
6261
- Message type moved from interface to `match()` method
63-
- All operations return `Trampoline` for stack-safe execution
62+
- Stack-safe execution for deep compositions
6463
- Explicit `Error` type parameter for better type safety
6564

6665
### EffectResult<State, Result>

docs/effect_monad_guide.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,9 @@ counter.tell(new Increment(5));
105105

106106
1. **Message Queued**: The message enters the actor's mailbox (a queue)
107107
2. **Actor Wakes Up**: The actor's virtual thread picks up the message
108-
3. **Effect Executes**: The effect's runT() method is called with current state, message, and context
109-
4. **Trampoline Runs**: The effect returns a Trampoline which is immediately evaluated
110-
5. **State Updated**: If successful, the actor's state is updated
111-
6. **Next Message**: The actor processes the next message in the mailbox
108+
3. **Effect Executes**: The effect runs with current state, message, and context
109+
4. **State Updated**: If successful, the actor's state is updated
110+
5. **Next Message**: The actor processes the next message in the mailbox
112111

113112
### Key Insights
114113

@@ -1062,7 +1061,7 @@ Effect<Int, Throwable, String> effect =
10621061
// When the actor runs the effect, it executes immediately
10631062
EffectResult<Int, String> result = effect.run(state, message, context);
10641063

1065-
// The trampoline ensures stack safety, but execution is eager:
1064+
// Execution is eager and stack-safe:
10661065
// 1. modify runs
10671066
// 2. log runs
10681067
// 3. map runs
@@ -1074,7 +1073,7 @@ EffectResult<Int, String> result = effect.run(state, message, context);
10741073
1. **Effects are values** - You can store them, pass them around, compose them
10751074
2. **Composition is lazy** - Chaining effects doesn't execute them
10761075
3. **Execution is eager** - Once `run()` is called, the effect runs to completion
1077-
4. **Trampolining is transparent** - Stack safety doesn't change eager semantics
1076+
4. **Stack safety is transparent** - You can chain thousands of operations without worry
10781077

10791078
### Suspended Computations
10801079

docs/throwable_effect_api.md

Lines changed: 10 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
## Overview
44

5-
`ThrowableEffect<State, Result>` is a simplified, stack-safe alternative to `Effect<State, Message, Result>`. It removes the Message type parameter, making the API less verbose while maintaining full functionality through the use of a `Trampoline` for stack safety.
5+
`ThrowableEffect<State, Result>` is a simplified, stack-safe alternative to `Effect<State, Message, Result>`. It removes the Message type parameter, making the API less verbose while maintaining full stack-safe functionality.
66

77
## Key Features
88

99
- **Less Verbose** - Only 2 type parameters instead of 3
10-
- **Stack-Safe** - Uses `Trampoline` to prevent StackOverflowError on deep compositions
10+
- **Stack-Safe** - Prevents StackOverflowError on deep compositions (chain thousands of operations safely)
1111
- **Built-in Error Channel** - Throwable handling is part of the type
1212
- **Message Type at Match** - Type constraint only where needed
1313
- **Fully Compatible** - All operators from Effect are available
@@ -64,10 +64,10 @@ ThrowableEffect<Integer, String> effect = ThrowableEffect.fail(new RuntimeExcept
6464

6565
## Stack Safety
6666

67-
The key innovation of `ThrowableEffect` is its use of `Trampoline` for stack-safe evaluation:
67+
`ThrowableEffect` is designed to handle deep effect compositions without stack overflow:
6868

6969
```java
70-
// This would cause StackOverflowError with regular Effect
70+
// Chain thousands of operations safely
7171
ThrowableEffect<Integer, Integer> effect = ThrowableEffect.of(0);
7272

7373
for (int i = 0; i < 10000; i++) {
@@ -78,22 +78,7 @@ for (int i = 0; i < 10000; i++) {
7878
EffectResult<Integer, Integer> result = effect.run(0, msg, context);
7979
```
8080

81-
### How It Works
82-
83-
Instead of eagerly evaluating compositions, `ThrowableEffect` returns a `Trampoline` that describes the computation:
84-
85-
```java
86-
@FunctionalInterface
87-
public interface ThrowableEffect<S, R> {
88-
// Returns a Trampoline for lazy, stack-safe evaluation
89-
Trampoline<EffectResult<S, R>> runT(S state, Object message, ActorContext context);
90-
91-
// Convenience method that runs the trampoline
92-
default EffectResult<S, R> run(S state, Object message, ActorContext context) {
93-
return runT(state, message, context).run();
94-
}
95-
}
96-
```
81+
This means you can build complex effect pipelines by composing many operations together without worrying about stack depth limitations.
9782

9883
## Monadic Operations
9984

@@ -201,41 +186,12 @@ ThrowableEffect<Integer, Void> counter = ThrowableEffect.<Integer>match()
201186
.when(Decrement.class, (state, msg, ctx) ->
202187
ThrowableEffect.modify(s -> s - msg.amount())
203188
)
204-
.when(GetCount.class, (state, msg, ctx) ->
205-
ThrowableEffect.<Integer, Void>state()
206-
.andThen((state2, msg2, ctx2) -> {
207-
msg.replyTo().tell(state2);
208-
return Trampoline.done(EffectResult.noResult(state2));
209-
})
189+
.when(GetCount.class, (state, msg, ctx) ->
190+
ThrowableEffect.tell(msg.replyTo(), state)
210191
)
211192
.build();
212193
```
213194

214-
## Trampoline API
215-
216-
The `Trampoline` data structure enables stack-safe recursion:
217-
218-
```java
219-
// Create a completed trampoline
220-
Trampoline<Integer> done = Trampoline.done(42);
221-
222-
// Suspend a computation
223-
Trampoline<Integer> suspended = Trampoline.more(() ->
224-
Trampoline.done(42)
225-
);
226-
227-
// Delay a computation
228-
Trampoline<Integer> delayed = Trampoline.delay(() -> expensiveComputation());
229-
230-
// Map and flatMap are stack-safe
231-
Trampoline<Integer> result = trampoline
232-
.map(x -> x * 2)
233-
.flatMap(x -> Trampoline.done(x + 10));
234-
235-
// Run the trampoline
236-
Integer value = result.run(); // Iterative evaluation - no stack growth
237-
```
238-
239195
## When to Use ThrowableEffect vs Effect
240196

241197
### Use ThrowableEffect When:
@@ -266,7 +222,7 @@ ThrowableEffect<State, Result> newEffect = ...;
266222
## Performance
267223

268224
- **Stack Safety**: Prevents StackOverflowError for deep compositions
269-
- **Overhead**: Minimal - trampoline adds one level of indirection
225+
- **Overhead**: Minimal - negligible performance cost for stack safety
270226
- **Parallel Operations**: Same performance as Effect (uses CompletableFuture)
271227
- **Memory**: Slightly lower than Effect (one less type parameter)
272228

@@ -281,21 +237,12 @@ ThrowableEffect<State, Result> newEffect = ...;
281237
ThrowableEffect.modify(s -> s)
282238
```
283239

284-
2. **Prefer `filterOrElse()` over manual validation**
240+
2. **Use `filterOrElse()` for conditional validation**
285241
```java
286-
// Good
287242
effect.filterOrElse(
288243
s -> s.isValid(),
289244
fallbackEffect
290245
)
291-
292-
// Manual
293-
effect.andThen((s, m, c) -> {
294-
if (!s.isValid()) {
295-
return fallbackEffect.runT(s, m, c);
296-
}
297-
return Trampoline.done(EffectResult.noResult(s));
298-
})
299246
```
300247

301248
3. **Use `attempt()` for exception-prone operations**
@@ -316,4 +263,4 @@ ThrowableEffect<State, Result> newEffect = ...;
316263

317264
## Summary
318265

319-
`ThrowableEffect` provides a cleaner, stack-safe API for building functional actors in Cajun. Its simplified type signature and built-in trampoline make it ideal for complex effect compositions while maintaining full compatibility with the existing Effect ecosystem.
266+
`ThrowableEffect` provides a cleaner, stack-safe API for building functional actors in Cajun. Its simplified type signature makes it ideal for complex effect compositions while maintaining full compatibility with the existing Effect ecosystem.

0 commit comments

Comments
 (0)