Demonstrating Monadic Computations Using Maybe Monad¶
In this tutorial, we will build a small safe calculation pipeline that chains operations which can fail, using the bind operator (|). Along the way, we will encounter safe_sqrt, safe_reciprocal, and Maybe[float].ret — and see how the chain short-circuits cleanly when any step fails.
Prerequisites¶
Complete the Getting Started with Katharos tutorial.
Complete the Handling Null Values with Maybe tutorial so you are familiar with
Maybe[T].JustandMaybe[T].Nothing.
Step 1: Create the Script and a Failing Function¶
First, we create a new file called pipeline.py and add a function that returns a Maybe:
from katharos.types import Maybe
def safe_sqrt(x: float) -> Maybe[float]:
if x < 0:
return Maybe[float].Nothing()
return Maybe[float].Just(x ** 0.5)
print(safe_sqrt(16.0))
print(safe_sqrt(-4.0))
Now, run the file:
python pipeline.py
The output should look like this:
Just(4.0)
Nothing()
Notice how safe_sqrt always returns a value wrapped in Maybe.
Step 2: See the Problem with fmap¶
Next, we try to apply safe_sqrt to a value already inside a Maybe using fmap. Replace the two print lines with:
result = Maybe[float].Just(16.0).fmap(safe_sqrt)
print(result)
Run the file again. The output should look like this:
Just(Just(4.0))
Notice the nested Just(Just(...)). That is the problem we are about to fix.
Step 3: Use Bind to Flatten the Result¶
Now, we replace fmap with the bind operator |, which is just an infix shorthand for the .bind(...) method. Change the result line to:
result = Maybe[float].Just(16.0) | safe_sqrt # or Maybe[float].Just(16.0).bind(safe_sqrt)
print(result)
Run the file again. The output should look like this:
Just(4.0)
Notice the result is flat. The | operator applies safe_sqrt and unwraps the extra layer for us.
Step 4: Chain Two Operations¶
Next, we add a second failing function and chain it after safe_sqrt. Add this function below safe_sqrt:
def safe_reciprocal(x: float) -> Maybe[float]:
if x == 0:
return Maybe[float].Nothing()
return Maybe[float].Just(1 / x)
Then replace the result block with:
result = Maybe[float].Just(16.0) | safe_sqrt | safe_reciprocal
print(result)
Run the file. The output should look like this:
Just(0.25)
Notice how the value flows through both steps: 16.0 → 4.0 → 0.25.
Step 5: Watch the Chain Short-Circuit¶
Now, we feed a negative number into the same chain. Change the starting value to -16.0:
result = Maybe[float].Just(-16.0) | safe_sqrt | safe_reciprocal
print(result)
Run the file. The output should look like this:
Nothing()
Notice that safe_reciprocal was never reached. Once any step returns Nothing(), the rest of the chain is skipped automatically.
Step 6: Start the Chain with ret¶
Finally, we use Maybe[float].ret to start the chain from a plain value instead of writing Maybe[float].Just ourselves. Replace the result line with:
result = Maybe[float].ret(16.0) | safe_sqrt | safe_reciprocal
print(result)
Run the file. The output should look like this:
Just(0.25)
Notice the result is identical to Step 4. Maybe[float].ret simply wraps a plain value into the monad so the chain has a starting point.
What We Built¶
We built a pipeline that:
Wraps a starting value with
Maybe[float].ret.Chains failure-aware functions with
|.Produces a flat
Mayberesult.Short-circuits to
Nothing()as soon as any step fails.