# Program

This page describes the V4 `program` implementation, based on the **Tagless Final** pattern and an **async reader** design. It resides entirely in the [`Shopfoo.Program`](https://github.com/rdeneau/shopfoo/tree/main/src/Shopfoo.Program) project.

## Solution Structure

```txt
📂 src/
├──📂 Core/
│  ├──🗃️ Shopfoo.Common
│  ├──🗃️ Shopfoo.Domain.Types
│  └──🗃️ Shopfoo.Program           👈 Program infrastructure
│     ├──📄 Program.fs             👈 Program type, program CE
│     ├──📄 Saga.fs                👈 Undo types and logic
│     ├──📄 Runner.fs              👈 Workflow runner, instruction preparer
│     ├──📄 Metrics.fs             👈 Observability
│     └──📄 Dependencies.fs        👈 DI registration
├──📂 Feat/
│  ├──🗃️ Shopfoo.Home              👈 Simple features, without workflows
│  └──🗃️ Shopfoo.Product           👈 Complex features, with domain workflows
└──📂 UI/
   ├──🗃️ Shopfoo.Client
   ├──🗃️ Shopfoo.Server
   └──🗃️ Shopfoo.Shared
```

## Program Type

The `Program` type is a **type alias** — a function that takes an instruction set and returns an asynchronous result:

```fsharp
/// Marker interface to identify the set of instructions for a program.
type IProgramInstructions = interface end

/// Type alias for the constraint `'ins when 'ins :> IProgramInstructions`.
type Instructions<'ins when 'ins :> IProgramInstructions> = 'ins

/// A program that, given a set of instructions ('ins), produces an async result.
/// This is the ReaderT monad where the environment is the instruction set.
type Program<'ins, 'ret when Instructions<'ins>> = 'ins -> Async<'ret>

/// Shorthand for a result that may fail with the domain Error type.
type Res<'t> = Result<'t, Error>
```

**Key points:**

* `IProgramInstructions`: Marker interface that all instruction sets must inherit.
* `Instructions<'ins>`: Type alias encapsulating the constraint, so it can be changed in one place.
* `Program<'ins, 'ret>`: A function from instructions to an async result. This replaces the V3 free monad ADT (`Stop | Effect`) with a direct function.
* `Res<'t>`: Shorthand alias for `Result<'t, Error>`, used throughout the codebase wherever a computation may fail with a domain error.

## Program Module

The `Program` module provides the standard functional combinators:

```fsharp
[<RequireQualifiedAccess>]
module Program =
    let retn (a: 'a) : Program<'ins, 'a> =
        fun _ -> async { return a }

    let bind f (prog: Program<'ins, 'a>) : Program<'ins, 'b> =
        fun ins ->
            async {
                let! a = prog ins
                return! f a ins
            }

    let map (f: 'a -> 'b) (prog: Program<'ins, 'a>) : Program<'ins, 'b> =
        fun ins ->
            async {
                let! x = prog ins
                return f x
            }

    let map2 (f: 'a -> 'b -> 'c)
             (progA: Program<'ins, 'a>)
             (progB: Program<'ins, 'b>)
             : Program<'ins, 'c> =
        fun ins ->
            async {
                let! childTaskA = Async.StartChild(progA ins)
                let! b = progB ins
                let! a = childTaskA
                return f a b
            }
```

The `map2` function is essential: it enables **parallel execution** of two independent programs by starting the first as a child async computation, running the second, and then awaiting the first. This is what powers the applicative `let! ... and! ...` syntax.

## Computation Expression

The `ProgramBuilder` class provides the `program` CE:

```fsharp
type ProgramBuilder() =
    member _.Return(a: 'a) : Program<'ins, 'a> = Program.retn a
    member _.ReturnFrom(prog: Program<'ins, 'a>) = prog
    member _.Bind(prog: Program<'ins, 'a>, f: 'a -> Program<'ins, 'b>) = Program.bind f prog

    // Applicative support for parallel execution
    member _.Bind2Return(progA, progB, f) =
        Program.map2 (fun a b -> f (a, b)) progA progB
    member this.MergeSources(progA, progB) =
        this.Bind2Return(progA, progB, id)

let program = ProgramBuilder()
```

**Applicative syntax:**

* `Bind2Return` and `MergeSources` are the CE methods that enable `let! ... and! ...`. When the compiler sees `let! a = exprA` followed by `and! b = exprB`, it calls `MergeSources` (which calls `map2`) to run both computations concurrently.
* This is impossible with `Bind` alone, which is inherently sequential.

### Result Bind Overloads

The CE provides multiple `Bind` overloads so that `Result` values integrate seamlessly with programs:

```fsharp
type ProgramBuilder with
    // Bind a Program<Result<_, _>> — unwrap the Ok track, short-circuit on Error
    member _.Bind(progR: Program<'ins, Result<_, _>>, f) = progR >>= (bindResult f)

    // Bind a plain Result (e.g., from a domain type smart constructor)
    member _.Bind(result: Result<_, _>, f) = result |> bindResult f

    // Overloads for specific error types, lifting them to the common Error type
    member inline x.Bind(result: Result<_, DataRelatedError>, f) = ...
    member inline x.Bind(result: Result<_, OperationNotAllowedError>, f) = ...
    member inline x.Bind(result: Result<_, GuardClauseError>, f) = ...
```

This design eliminates manual error-track management in workflows — the CE handles the lifting and short-circuiting automatically.

### Result Aggregation

The CE does **not** provide a `MergeSources` overload for `Result`. Parallel applicative syntax (`let! ... and! ...`) only applies to `Program` values; it cannot aggregate multiple `Result` values into a combined result, collecting all errors.

Result aggregation must be handled **case by case** in the program, using the `Result.zip` helper:

```fsharp
/// Combines two Results into a pair on the Ok track,
/// or merges both errors into the Errors case of the domain Error type.
let Result.zip : Result<'a, Error> -> Result<'b, Error> -> Result<'a * 'b, Error>
```

Typical usage — validating several independent values before proceeding:

```fsharp
program {
    let r1 = ProductName.create name      // Result<ProductName, Error>
    let r2 = ProductPrice.create price    // Result<ProductPrice, Error>
    let! name, price = Result.zip r1 r2   // short-circuits if either fails, collecting both errors
    ...
}
```

If more than two results need to be combined, `Result.zip` can be chained, or a dedicated `Result.zipN` helper can be introduced for the specific arity.

### Defining Programs from Instructions

The `DefineProgram<'ins>` static class provides a single method to define a program from a single instruction:

```fsharp
type DefineProgram<'ins when Instructions<'ins>> =
    static member inline instruction
        ([<InlineIfLambda>] work: 'ins -> Async<'ret>)
        : Program<'ins, 'ret> = work
```

This is an **identity function** — it exists purely for developer experience. Usage:

```fsharp
type private DefineProgram = DefineProgram<IProductInstructions>

let getPrices sku   = DefineProgram.instruction _.GetPrices(sku)
let savePrices p    = DefineProgram.instruction _.SavePrices(p)
let addProduct prod = DefineProgram.instruction _.AddProduct(prod)
```

The type alias `DefineProgram = DefineProgram<IProductInstructions>` fixes the instruction set, so each call only requires the shorthand lambda `_.Method(args)` — which triggers IntelliSense on the instruction interface. This is perhaps the most ergonomic part of the whole design.

## Saga Support (Undo)

The `Saga.fs` file provides the types and logic for undoing completed instructions when a workflow fails — implementing the **Saga pattern** for synchronous, in-process workflows (no message bus involved).

### Undo Types

```fsharp
type UndoFunc([<InlineIfLambda>] func: unit -> Async<Res<unit>>) =
    member _.Invoke() = func ()

[<RequireQualifiedAccess>]
type Undo =
    | None
    | Revert of UndoFunc      // Strict undo: DELETE to reverse an INSERT
    | Compensate of UndoFunc  // Loose undo: RefundPayment to compensate ChargePayment
```

Each command instruction can be marked with an undo strategy:

* `Undo.None`: Not undoable (e.g., sending a notification).
* `Undo.Revert`: Strict reversal — restores the initial state exactly.
* `Undo.Compensate`: Compensation — produces a new operation that logically offsets the original.

### Step Tracking

Every instruction execution is recorded as a `ProgramStep`:

```fsharp
type ProgramStep = { Instruction: InstructionMeta; Status: StepStatus }

type StepStatus =
    | RunDone
    | RunFailed of runError: Error
    | UndoDone
    | UndoFailed of undoError: Error
```

The saga maintains a **LIFO history** of steps. On failure, it walks the history in reverse and executes each command's undo function.

### Saga Finalization

The `Saga.finalize` function determines whether to undo and executes the undo phase:

```fsharp
let finalize (canUndo: CanUndo) (result: Res<'t>) (history: ProgramStep list) : Async<SagaState>
```

It handles several cases:

* **Success:** Status = `Done`, no undo needed.
* **Workflow cancelled:** Status = `Cancelled`, no undo (cancellation is intentional).
* **Failure but undo not allowed** (predicate returns false): Status = `Failed`, no undo.
* **Failure with undo allowed:** Walks steps in reverse, executes undo functions, collects any undo errors.

## Workflow Runner

The `Runner.fs` file defines the infrastructure for running workflows with instruction preparation, monitoring, and saga integration.

### Instruction Preparer

The `IInstructionPreparer<'ins>` interface wraps raw instruction functions with monitoring and undo tracking:

```fsharp
[<Interface>]
type IInstructionPreparer<'ins when Instructions<'ins>> =
    abstract member Query:
        work: Work<'arg, 'ret option> * getName: ('arg -> string) -> Work<'arg, 'ret option>
    abstract member Command:
        work: Work<'arg, Res<'ret>> * getName: ('arg -> string) -> IWorkCommandBuilder<'arg, 'ret>
```

For **queries**, it wraps the raw function with logging, timing, and step tracking.

For **commands**, it returns an `IWorkCommandBuilder<'arg, 'ret>` on which the caller then specifies the undo strategy:

```fsharp
[<Interface>]
type IWorkCommandBuilder<'arg, 'ret> =
    abstract member NotUndoable: unit -> Work<'arg, Res<'ret>>
    abstract member Reversible: undoFun: ('arg -> 'ret -> Async<Res<unit>>) -> Work<'arg, Res<'ret>>
    abstract member Compensatable: undoFun: ('arg -> 'ret -> Async<Res<unit>>) -> Work<'arg, Res<'ret>>
```

The reason this is a **separate interface** — rather than a single `Command` method that takes both `work` and `undoFun` — is **F# type inference**. F# infers generic type parameters left-to-right within an expression, but it does not propagate inferred types across the arguments of a single method call. If `work` and `undoFun` were both parameters of `Command`, the compiler would not know `'arg` and `'ret` when it typechecks `undoFun`, forcing the caller to annotate the lambda explicitly.

By splitting into two calls, `Command(work, name)` infers `'arg` and `'ret` from `work`, and the returned `IWorkCommandBuilder<'arg, 'ret>` carries those types. The `undoFun` lambda passed to `.Reversible(...)` or `.Compensatable(...)` is then fully typed — resulting in clean, annotation-free code:

```fsharp
// 'arg = Prices, 'ret = unit — inferred from pricesPipeline.AddPrices
member _.AddPrices =
    prepare
        .Command(pricesPipeline.AddPrices)
        .Reversible(fun prices _ -> pricesPipeline.DeletePrices prices.SKU)
        //           ^^^^^^ typed as Prices — no annotation needed

// 'ret = PreviousValue<Prices> — inferred from pricesPipeline.SavePrices
member _.SavePrices =
    prepare
        .Command(pricesPipeline.SavePrices)
        .Reversible(fun _ (PreviousValue initialPrices) ->
            async {
                let! res = pricesPipeline.SavePrices initialPrices
                return res |> Result.ignore
            })
        //      ^^^^^^^^^^^^^^^^^^^ pattern match works without annotation
```

The fluent style is a pleasant side-effect, common in C#, but the primary motivation is type inference ergonomics.

### Auto-deriving instruction names with `[<CallerMemberName>]`

The extension methods on `IInstructionPreparer` use `[<CallerMemberName>]` to capture the instruction name automatically from the object expression member:

```fsharp
open System.Runtime.CompilerServices

type IInstructionPreparer<'ins when Instructions<'ins>> with
    member this.Query(work, [<CallerMemberName>] ?name) =
        this.Query(work, fun _ -> defaultArg name "")
    member this.Command(work, [<CallerMemberName>] ?name) =
        this.Command(work, fun _ -> defaultArg name "")
```

In an object expression like `{ new IProductInstructions with member _.GetPrices = prepare.Query(...) }`, `CallerMemberName` captures `"GetPrices"` — the name of the enclosing member. This eliminates the redundant string literal.

**Resolution rule:** F# resolves instance methods before extension methods. So:

* `prepare.Query(work)` — 1 argument, interface needs 2 → resolved by the extension method (auto-name)
* `prepare.Command(work, getName)` — 2 arguments, `getName` is a function → resolved by the interface method (dynamic name)

### Workflow Runner

The `IWorkflowRunner<'ins>` interface provides two execution modes:

```fsharp
[<Interface>]
type IWorkflowRunner<'ins when Instructions<'ins>> =
    abstract member Run:
        workflow: #IProgramWorkflow<'ins, 'arg, 'ret> ->
        arg: 'arg ->
        prepareInstructions: (IInstructionPreparer<'ins> -> 'ins) ->
            Async<Result<'ret, Error>>

    abstract member RunInSaga:
        workflow: #IProgramWorkflow<'ins, 'arg, 'ret> ->
        arg: 'arg ->
        prepareInstructions: (IInstructionPreparer<'ins> -> 'ins) ->
        undoPredicate: CanUndo ->
            Async<Result<'ret, Error> * SagaState>
```

* `Run`: Simple execution without undo support.
* `RunInSaga`: Execution with undo support — returns both the result and the saga state (history of all steps and their statuses).

The `prepareInstructions` parameter is a function that receives an `IInstructionPreparer` and returns the instruction set implementation. This is where each instruction is wired to its data-layer function and its undo strategy. This wiring happens in the domain's `Api` class.

## Dependency Injection

The `Dependencies.fs` file provides the DI registration:

```fsharp
type IServiceCollection with
    member services.AddProgram() =
        services
            .AddSingleton<IMetricsSender, MetricsLogger>()
            .AddSingleton<IWorkMonitors, WorkMonitors>()
            .AddSingleton<IWorkflowRunnerFactory, WorkflowRunnerFactory>()
```

The concrete types (`MetricsLogger`, `WorkMonitors`, `WorkflowRunnerFactory`) are defined inside a `module private Implementation` block, making them invisible outside the `Shopfoo.Program` assembly. Only the interfaces (`IMetricsSender`, `IWorkMonitors`, `IWorkflowRunnerFactory`) are public. This brings three benefits:

* **Encapsulation:** Implementation details cannot be referenced directly by consumers — they can only be reached through the interfaces.
* **Reduced API surface:** The public contract is limited to what callers actually need, preventing accidental coupling to internals.
* **Dependency inversion (SOLID):** Domain and feature projects depend on the abstractions, not the concrete types. Implementations can change or be swapped (e.g., for testing) without touching any call site.

The `IWorkflowRunnerFactory` creates `IWorkflowRunner<'ins>` instances for a given domain name. Each domain project calls `workflowRunnerFactory.Create(domainName)` to obtain its runner.

## Summary

| Component         | Responsibility                                                    |
| ----------------- | ----------------------------------------------------------------- |
| `Program.fs`      | Program type (async reader), CE builder with applicative support  |
| `Saga.fs`         | Undo types (`Undo`, `UndoFunc`), step tracking, saga finalization |
| `Runner.fs`       | Workflow runner, instruction preparer with monitoring and undo    |
| `Metrics.fs`      | Timing and metrics for instructions                               |
| `Dependencies.fs` | DI registration                                                   |

The V4 program achieves:

* **Radical simplicity:** Program is a function, not an ADT.
* **Parallel execution:** `let! ... and! ...` runs instructions concurrently via `map2`.
* **Undo support:** Saga pattern with reversible and compensatable commands.
* **Observability:** Logging, timing, and step tracking built into the runner infrastructure.
* **Domain isolation:** Each domain defines its own instruction interface and wires it independently.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://rdeneau.gitbook.io/safe-clean-architecture/domain-workflows/2-program.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
