diff --git a/CLAUDE.md b/CLAUDE.md index 200b003..8e06c25 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -44,16 +44,6 @@ The converter follows a compiler-like architecture with three phases: | `(*** include-python: symbol ***)` | Include generated Python for symbol | | Regular F# code | Wrapped in ```fsharp fenced blocks | -### Escaping F# in Headings - -Use `` F`#` `` (backticks around `#`) in markdown headings to prevent markdownlint from interpreting it as ATX closed style: - -```fsharp -(** -## F`#` Async Workflows -*) -``` - ### File Structure ```text @@ -68,6 +58,7 @@ chapters/ ├── Testing.fs # Testing F# code with Python test runners ├── FableV5.fs # Fable v5 features, Rust core, PyPI ├── Pydantic.fs # Pydantic models, DTOs, validation +├── FastAPI.fs # Type-safe web APIs with FastAPI ├── UnitsOfMeasure.fs # Compile-time dimensional analysis ├── FableLiterate.fs # Symlink → ../Fable.Literate/App.fs └── Summary.fs # Wrap-up, resources, repo link diff --git a/Fable.Literate/App.fs b/Fable.Literate/App.fs index 8a479ab..de70896 100644 --- a/Fable.Literate/App.fs +++ b/Fable.Literate/App.fs @@ -23,7 +23,7 @@ Fable.Python isn't just a toy: you're looking at a real project that works. ## How It Works -The converter follows a compiler-like architecture with three phases: +The converter follows a compiler-like architecture with three phases (just like Fable itself): 1. **Parse**: Convert source lines into a Block AST 2. **Transform**: Filter hidden blocks, resolve Python includes diff --git a/chapters/AsyncProgramming.fs b/chapters/AsyncProgramming.fs index 4c8fefb..a1a01ec 100644 --- a/chapters/AsyncProgramming.fs +++ b/chapters/AsyncProgramming.fs @@ -7,7 +7,7 @@ Asynchronous programming is essential for modern applications - from web APIs to processing pipelines. F# offers two models for async code: `async` workflows and `task` expressions. Understanding when to use each is key to effective Fable.Python development. -## Comparing Python and F`#` Async Models +## Comparing Python and F# Async Models Python's async model is built on `asyncio`. Python coroutines are **cold** - calling an `async def` function returns a coroutine object that doesn't execute until awaited: @@ -110,7 +110,7 @@ let catchExample () = } (** -## F`#` Tasks +## F# Tasks The `task` computation expression in .NET creates *hot* tasks that start immediately. However, when compiled to Python via Fable, tasks become Python coroutines - which are diff --git a/chapters/Compatibility.fs b/chapters/Compatibility.fs index 1a2bb69..66df014 100644 --- a/chapters/Compatibility.fs +++ b/chapters/Compatibility.fs @@ -47,7 +47,7 @@ Most FSharp.Core operators are supported, including formatting with `sprintf`, | F# Type | Python | | ----------------- | -------------------------- | | `Tuple` | `tuple` | -| `Option` | erased to `T \| None` | +| `Option` | `Optional[T]` (*) | | `string` | `str` | | `List` | `List.fs` (immutable list) | | `Map` | `Map.fs` (immutable map) | @@ -56,6 +56,8 @@ Most FSharp.Core operators are supported, including formatting with `sprintf`, | Record types | `@dataclass` | | Anonymous Records | `dict` | +(*) Generated as `T | None` in Python 3.12+ + ## Interfaces and Protocols .NET interfaces map to Python protocols and special methods: diff --git a/chapters/FableV5.fs b/chapters/FableV5.fs index bbee3fb..018f0b8 100644 --- a/chapters/FableV5.fs +++ b/chapters/FableV5.fs @@ -102,7 +102,7 @@ dotnet tool install fable --version 5.0.0-alpha.21 dotnet add package Fable.Core --version 5.0.0-beta.4 # Install the Python runtime -uv add fable-library==5.0.0a17 +uv add fable-library==5.0.0a21 ``` Then compile your F# to Python: diff --git a/chapters/FastAPI.fs b/chapters/FastAPI.fs index 58105f8..3ea8962 100644 --- a/chapters/FastAPI.fs +++ b/chapters/FastAPI.fs @@ -56,11 +56,11 @@ let app = FastAPI(title = "My API", version = "1.0.0") (** This generates: +*) -```python -app = FastAPI(title="My API", version="1.0.0") -``` +(*** include-python: app ***) +(** The `app` variable name is important - the route decorators reference it. ## Defining Models diff --git a/chapters/Introduction.fs b/chapters/Introduction.fs index f35c169..dfff1e1 100644 --- a/chapters/Introduction.fs +++ b/chapters/Introduction.fs @@ -77,7 +77,7 @@ Fable.Python is a great choice when: Let's start with F# code that compiles to Python: *) -let greet name = $"Hello, {name}!" +let greet (name: string) = $"Hello, {name}!" let message = greet "Fable.Python" @@ -88,10 +88,9 @@ When compiled with Fable, this generates the following Python: (*** include-python: greet, message ***) (** -The `name: Any | None = None` signature may look odd at first. This happens because F# infers the type from usage - -since we only call `greet` with a string, the compiler doesn't know if it might also be called with unit `()` (no -argument). If it were, Python would call it as `greet()` instead of `greet("Fable.Python")`. Adding an explicit type -annotation `let greet (name: string) = ...` would generate a cleaner `name: str` parameter. +Notice how the explicit type annotation `(name: string)` generates clean Python with `name: str`. +Without it, F# infers from usage and Fable generates `name: Any | None = None` to handle cases +where the function might be called with no argument. Type annotations give you cleaner output. ## The Power of Types diff --git a/chapters/Pydantic.fs b/chapters/Pydantic.fs index 02a726c..d963242 100644 --- a/chapters/Pydantic.fs +++ b/chapters/Pydantic.fs @@ -178,17 +178,19 @@ This pattern is useful when you want to: F# types map naturally to Python/Pydantic types: -| F# Type | Python Type | Notes | -| ----------- | ------------ | ------------------------------ | -| `string` | `str` | | -| `int` | `int` | | -| `float` | `float` | | -| `bool` | `bool` | | -| `'T option` | `T \| None` | Modern union syntax | -| `'T list` | `list[T]` | | -| `'T array` | `list[T]` | | -| Record | `class` | With `@dataclass` or BaseModel | -| DU | Tagged class | See below | +| F# Type | Python Type | Notes | +| ----------- | ----------------- | ------------------------------ | +| `string` | `str` | | +| `int` | `int` | | +| `float` | `float` | | +| `bool` | `bool` | | +| `'T option` | `Optional[T]` (*) | Modern union syntax | +| `'T list` | `list[T]` | | +| `'T array` | `list[T]` | | +| Record | `class` | With `@dataclass` or BaseModel | +| DU | Tagged class | See below | + +(*) Generated as `T | None` in Python 3.12+ ### F# Option to Python Union diff --git a/chapters/Python.fs b/chapters/Python.fs index 8d41a91..7b70d90 100644 --- a/chapters/Python.fs +++ b/chapters/Python.fs @@ -203,7 +203,7 @@ unwrap the option first. | Dictionary | `{"a": 1}` | `Map.ofList [("a", 1)]` | | None check | `if x is None:` | `match x with None ->` | | String format | `f"Hello {name}"` | `$"Hello {name}"` | -| Type annotation | `x: int` | `x: int32`. | +| Type annotation | `x: int` | `x: int32` | | Comments | `# comment` | `// comment` | | Multiline string | `"""text"""` | `"""text"""` (same!) | diff --git a/chapters/Testing.fs b/chapters/Testing.fs index 6d00744..a004212 100644 --- a/chapters/Testing.fs +++ b/chapters/Testing.fs @@ -119,7 +119,7 @@ same API to Fable, supporting JavaScript, Python, and .NET. - **Composable**: Tests are values you can combine and transform - **No magic**: No reflection, no attributes - just functions -- **Familiar F# idioms**: Uses computation expressions and pipelines +- **Familiar F# idioms**: Uses lists and pipelines ### Setting Up Pyxpecto