Syntax Sugar Module

Syntactic sugar for monadic operations.

This module provides convenient syntax for working with monads, making monadic code more readable and maintainable.

The do() decorator provides Haskell-style do-notation for sequencing monadic computations using generator functions.

Do Notation

katharos.syntax_sugar.do(monad_type)[source]

Decorator for generator-based do-notation.

Each yield monad extracts the wrapped value, analogous to <- in Haskell. The unwrapped value is immediately available for use in subsequent yields.

Annotate the generator with DoBlock[R] where R is the plain return type. Annotate individual yield sites inline for per-binding types:

x: int = yield Maybe.Just(3)
y: str = yield Maybe.Just("hi")

A plain return value is automatically lifted via monad_type.ret(). Returning an already-monadic value passes it through unchanged.

Short-circuiting is handled transparently by bind: if any yielded monad is Nothing or Failure, the rest of the generator is abandoned and that monad is returned immediately.

Works correctly with non-deterministic monads such as ImmutableList whose bind calls its function more than once. Each branch replays the generator from the start with the accumulated history of sent values, so every execution path sees a fresh generator in the right state.

Note

Side effects in the generator body (between yield expressions) will be replayed once per branch. Keep the generator body pure; place any side effects inside the yielded monads themselves.

Parameters:

monad_type (type[TypeVar(M, bound= Monad)]) – The concrete monad class (e.g. Maybe, Result).

Return type:

Callable[[Callable[..., GenericAlias[TypeVar(R)]]], Callable[..., TypeVar(M, bound= Monad)]]

Returns:

A decorator that transforms a generator function into an ordinary function returning a value of type M.

Examples

>>> from katharos.types import Maybe
>>> @do(Maybe)
... def computation() -> DoBlock[int]:
...     x: int = yield Maybe.Just(3)
...     y: int = yield Maybe.Just(x + 1)  # x is 3 here
...     return x + y
>>> computation()
Just(7)
>>> @do(Maybe)
... def short_circuit() -> DoBlock[int]:
...     x: int = yield Maybe.Just(10)
...     y: int = yield Maybe.Nothing()    # stops here
...     return x + y                      # never reached
>>> short_circuit()
Nothing()
katharos.syntax_sugar.DoBlock = DoBlock

Type alias.

Type aliases are created through the type statement:

type Alias = int

In this example, Alias and int will be treated equivalently by static type checkers.

At runtime, Alias is an instance of TypeAliasType. The __name__ attribute holds the name of the type alias. The value of the type alias is stored in the __value__ attribute. It is evaluated lazily, so the value is computed only if the attribute is accessed.

Type aliases can also be generic:

type ListOrSet[T] = list[T] | set[T]

In this case, the type parameters of the alias are stored in the __type_params__ attribute.

See PEP 695 for more information.