funstruct playground

All demo scripts from the funstruct repo, runnable in the browser via Pyodide.

Requires funstruct >= 2.0.0 on PyPI.
⏳ Loading Python + funstruct...

Demo: AsyncResult pipeline — composing async operations that can fail

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

source: demos/01_result_pipeline.py

⚠ This demo uses async — view-only in browser. Run locally: uv run python demos/01_result_pipeline.py

Demo: do-notation — generator-based monadic sequencing

@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

source: demos/02_do_notation.py

⚠ This demo uses async — view-only in browser. Run locally: uv run python demos/02_do_notation.py

Demo: tagless final — write once, swap effects

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

source: 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

Demo: tagless final — swapping database backends

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

source: 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

Demo: JSON encoder — typeclass composition and derivation

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

source: demos/05_json_encoder.py

Demo: generic functions with trait bounds (F: Monad)

This 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

source: demos/06_generic_functions.py

Demo: Reader monad vs Cake pattern for dependency injection

Both 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

source: demos/07_reader_vs_cake.py

Demo: extending funstruct with your own types and typeclasses

This 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

source: demos/08_custom_types.py

Lenses: composable getters and setters for immutable data

The 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

source: demos/09_lenses.py

Writer monad: accumulate a call trace alongside computation

Writer[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

source: demos/10_writer_stacktrace.py

State monad: pure stateful computation without mutation

State[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

source: demos/11_state_counter.py

Trait bounds: constraining generic functions to types with specific capabilities

In Scala: def sort[A: Ordering](xs: List[A]): List[A] In Rust: fn sort(xs: &mut [A]) In Haskell: sort :: Ord a => [a] -> [a] In Python: summon enforces the bound at runtime The bound sa

source: demos/12_trait_bounds.py

Embedded DSL via tagless final — no parser needed

A 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

source: demos/13_dsl.py

Two ways to build a DSL: initial encoding vs tagless final

Same 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

source: demos/14_dsl_initial_vs_final.py

The typeclass pattern — explained with JSON serialization

This 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

source: demos/15_typeclass_pattern_json.py

The problem: nested monads are painful to compose

When 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) ->

source: 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

OptionT flattens nested Future[Option[A]] into a single pipeline

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

source: 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

Alternative: skip transformers by using one monad type everywhere

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

source: demos/transformers/03_alternative.py

⚠ This demo uses async — view-only in browser. Run locally: uv run python demos/transformers/03_alternative.py

github · pypi