All demo scripts from the funstruct repo, runnable in the browser via Pyodide.
funstruct >= 2.0.0 on PyPI.Shows two equivalent ways to build a pipeline: 1. Bind chains: .bind(lambda x: ...).map(lambda x: ...) 2. >> operator: pipeline >> next_step Both short-circuit on the first Err. Run: uv run
demos/01_result_pipeline.py⚠ This demo uses async — view-only in browser. Run locally: uv run python demos/01_result_pipeline.py
@do turns a generator function into a monadic pipeline. yield extracts the value from each monadic step; short-circuits on failure. Key rules: - @Result.do / @Option.do uses generators (def + yie
demos/02_do_notation.py⚠ This demo uses async — view-only in browser. Run locally: uv run python demos/02_do_notation.py
Tagless final abstracts over the effect type via a Protocol (algebra). The program is written once against the protocol. Swap the interpreter to change the effect — AsyncResult in prod, plain Result i
demos/03_tagless_final_intro.py⚠ This demo uses async — view-only in browser. Run locally: uv run python demos/03_tagless_final_intro.py
The real payoff: business logic (place_order) doesn't know or care whether it's talking to Postgres, an in-memory dict, or a test stub. Three interpreters, same program: PostgresOrderRepo — "rea
demos/04_tagless_final_db.py⚠ This demo uses async — view-only in browser. Run locally: uv run python demos/04_tagless_final_db.py
Demonstrates the "implicit typeclass" pattern using a JSON encoder. The architecture is the same as Scala's given/using or Haskell's typeclasses. The key insight: generic functions with "trait bounds
demos/05_json_encoder.pyThis is the canonical way to write effect-polymorphic programs in funstruct v2. Two ways to use the library: 1. Dot syntax (default, like Haskell) — Some(10).map(f).bind(g) 2. Typeclass insta
demos/06_generic_functions.pyBoth solve the same problem: threading dependencies through a pipeline without passing them explicitly at every call site. Reader monad: runtime DI — context threaded monadically Cake pattern: defi
demos/07_reader_vs_cake.pyThis shows how to: 1. Define a typeclass (interface) 2. Create a custom data type 3. Implement the typeclass for your type 4. Write generic functions with trait bounds This is the Sca
demos/08_custom_types.pyThe problem: updating deeply nested immutable structures requires rebuilding the entire path manually. # Without lenses: config.put("app", config["app"].put("db", config["app"]["db"].put("hos
demos/09_lenses.pyWriter[W, A] = (value: A, output: W) where W is a Monoid. The Writer monad lets you accumulate output (logs, traces, metrics) alongside a computation without passing a mutable log around. This demo
demos/10_writer_stacktrace.pyState[S, A] = S → (S, A) The State monad threads state through a pipeline without mutation. Each step reads and/or modifies the state, and the final state is returned alongside the result. Use cases
demos/11_state_counter.pyIn Scala: def sort[A: Ordering](xs: List[A]): List[A] In Rust: fn sort
demos/12_trait_bounds.pyA DSL (Domain Specific Language) is a small language for a specific problem. Tagless final lets you build one embedded in Python: 1. Define the algebra (what operations exist) 2. Write progra
demos/13_dsl.pySame arithmetic DSL, two fundamentally different approaches. Initial (AST-based): Build a data tree, then walk it with interpreters. + Can inspect/optimize the tree before running + Patte
demos/14_dsl_initial_vs_final.pyThis is the canonical example of how typeclasses work: 1. Define the typeclass (JSONWrite — the interface) 2. Create instances for primitives (str, int, bool, None) 3. Create COMPOSABLE i
demos/15_typeclass_pattern_json.pyWhen your functions return different monadic types, you end up with nested pattern matching at every step. get_user(name) -> Future[Option[User]] # might not exist, async get_age(user) ->
demos/transformers/01_the_problem.py⚠ This demo uses async — view-only in browser. Run locally: uv run python demos/transformers/01_the_problem.py
Instead of nested pattern matching, OptionT lets you write a flat pipeline that short-circuits on Nothing and awaits Futures automatically. The key operations: OptionT(future_option) — wrap a
demos/transformers/02_option_t.py⚠ This demo uses async — view-only in browser. Run locally: uv run python demos/transformers/02_option_t.py
Instead of composing Future[Option[A]] with OptionT, make ALL functions return the same type — AsyncResult[A]. Then composition is just .bind(). This is often simpler than transformers for real appli
demos/transformers/03_alternative.py⚠ This demo uses async — view-only in browser. Run locally: uv run python demos/transformers/03_alternative.py