nw-fp-fsharp
F# language-specific patterns, Railway-Oriented Programming, and Computation Expressions
Best use case
nw-fp-fsharp is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
F# language-specific patterns, Railway-Oriented Programming, and Computation Expressions
Teams using nw-fp-fsharp should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/nw-fp-fsharp/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How nw-fp-fsharp Compares
| Feature / Agent | nw-fp-fsharp | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
F# language-specific patterns, Railway-Oriented Programming, and Computation Expressions
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# FP in F# -- Functional Software Crafter Skill
Cross-references: [fp-principles](../nw-fp-principles/SKILL.md) | [fp-domain-modeling](../nw-fp-domain-modeling/SKILL.md) | [pbt-dotnet](../nw-pbt-dotnet/SKILL.md)
## When to Choose F#
- Best for: domain modeling on .NET | DDD | railway-oriented programming | pipeline-first design | finance
- Not ideal for: teams needing higher-kinded types | non-.NET platforms | large existing C# codebases resistant to change
## [STARTER] Quick Setup
```bash
dotnet new console -lang F# -o OrderService && cd OrderService
dotnet new xunit -lang F# -o OrderService.Tests
dotnet add OrderService.Tests reference OrderService
dotnet add OrderService.Tests package FsCheck.Xunit
dotnet test
```
**File order matters**: F# compiles files top-to-bottom as listed in `.fsproj`. Types must be defined before use.
## [STARTER] Type System for Domain Modeling
### Choice Types (Discriminated Unions)
```fsharp
type PaymentMethod =
| CreditCard of cardNumber: string * expiryDate: string
| BankTransfer of accountNumber: string
| Cash
```
### Record Types and Domain Wrappers
```fsharp
type Customer = {
CustomerId: CustomerId
CustomerName: CustomerName
CustomerEmail: EmailAddress
}
type OrderId = OrderId of int
type EmailAddress = EmailAddress of string
```
Records have structural equality by default. Single-case DUs have small runtime cost (unlike Haskell's zero-cost newtype).
### [STARTER] Validated Construction (Smart Constructors)
```fsharp
module EmailAddress =
let create (rawEmail: string) : Result<EmailAddress, string> =
if rawEmail.Contains("@") then Ok (EmailAddress rawEmail)
else Error $"Invalid email: {rawEmail}"
let value (EmailAddress email) = email
```
## [INTERMEDIATE] Composition Style
### Pipeline Operator (The Defining Feature)
```fsharp
let processOrder rawOrder =
rawOrder
|> validateOrder
|> Result.bind priceOrder
|> Result.bind confirmOrder
|> Result.map generateReceipt
```
**Data-last convention**: F# functions put primary input last so they compose with `|>`.
### Railway-Oriented Programming (Error-Track Pipelines)
```fsharp
let placeOrder unvalidatedOrder =
unvalidatedOrder
|> validateOrder
|> Result.bind priceOrder
|> Result.bind confirmOrder
|> Result.mapError PlaceOrderError.Validation
```
### Computation Expressions for Monadic Syntax
```fsharp
open FsToolkit.ErrorHandling
let placeOrder rawOrder = result {
let! validated = validateOrder rawOrder
let! priced = priceOrder validated
return! confirmOrder priced
}
```
Key builders: `result { }` (error-track) | `async { }` (async I/O) | `task { }` (.NET Task interop) | `validation { }` (accumulate errors, FsToolkit).
## [INTERMEDIATE] Effect Management
F# is impure by default. Purity maintained by architectural convention, not the compiler.
### Pure Core / Imperative Shell
```fsharp
// Pure domain logic (no I/O, no mutation)
module Domain =
let calculateDiscount (order: Order) : Discount =
if List.length order.OrderLines > 10 then Discount 0.1m
else Discount 0.0m
// Imperative shell (I/O at edges)
module App =
let placeOrderHandler (deps: Dependencies) (rawOrder: UnvalidatedOrder) = async {
let! result =
rawOrder
|> Domain.validateOrder deps.CheckProductExists
|> Result.bind (Domain.priceOrder deps.GetProductPrice)
do! deps.SaveOrder result
return result
}
```
### [ADVANCED] Hexagonal Architecture via Partial Application
```fsharp
// Ports as function types
type FindOrder = OrderId -> Async<Order option>
type SaveOrder = Order -> Async<unit>
// Adapter: concrete implementation
let findOrderInDb (connStr: string) (orderId: OrderId) : Async<Order option> =
async { (* database query *) }
// Composition root: partially apply dependencies
let findOrder = findOrderInDb "Server=localhost;Database=orders"
```
Dependencies first, primary input last. Partially apply at composition root.
## [INTERMEDIATE] Testing
**Frameworks**: FsCheck (QuickCheck port) | fsharp-hedgehog (integrated shrinking) | Expecto (F#-native) | Unquote (assertions). See [pbt-dotnet](../nw-pbt-dotnet/SKILL.md) for detailed PBT patterns.
### Property Test Example (FsCheck + xUnit)
```fsharp
open FsCheck.Xunit
[<Property>]
let ``validated orders always have positive totals`` (rawOrder: RawOrder) =
match validateOrder rawOrder with
| Error _ -> true
| Ok valid -> orderTotal valid > Money 0m
[<Property>]
let ``serialization round-trips`` (order: Order) =
order |> serialize |> deserialize = Ok order
```
### Custom Generator
```fsharp
let genValidEmail = gen {
let! user = Gen.nonEmptyListOf (Gen.elements ['a'..'z']) |> Gen.map (fun cs -> System.String(Array.ofList cs))
let! domain = Gen.nonEmptyListOf (Gen.elements ['a'..'z']) |> Gen.map (fun cs -> System.String(Array.ofList cs))
return EmailAddress $"{user}@{domain}.com"
}
```
## [ADVANCED] Idiomatic Patterns
### Document Lifecycle as Separate Types
```fsharp
type UnvalidatedOrder = { RawName: string; RawEmail: string; RawLines: string list }
type ValidatedOrder = { Name: CustomerName; Email: EmailAddress; Lines: OrderLine list }
type PricedOrder = { ValidOrder: ValidatedOrder; Total: Money; Lines: PricedOrderLine list }
```
Each stage is a distinct type. Pipeline transforms one into the next.
### Collect-All-Errors Validation
```fsharp
open FsToolkit.ErrorHandling
let validateCustomer (raw: RawCustomer) = validation {
let! name = validateName raw.Name
and! email = validateEmail raw.Email
and! address = validateAddress raw.Address
return { Name = name; Email = email; Address = address }
}
```
**Project structure**: Domain types/workflows in `OrderService.Domain/` | adapters in `OrderService.Infrastructure/` | composition root in `OrderService.App/`. File ordering in `.fsproj` defines compilation order.
## Maturity and Adoption
- **.NET dependency**: Deployment outside .NET (native, WASM) is limited. Tooling improvements lag behind C#.
- **Smaller community**: Fewer libraries, tutorials, Stack Overflow answers than C#. Community is helpful but small.
- **File ordering constraint**: Top-to-bottom compilation prevents circular dependencies (benefit) but frustrates developers used to free ordering. Refactoring file order is a real cost.
- **Second-class .NET citizen**: New .NET features (Blazor, MAUI) often ship C#-first with delayed or incomplete F# support.
## Common Pitfalls
1. **File order dependency**: Types in `B.fs` cannot reference `A.fs` if `A.fs` listed after `B.fs`. Reorder files when adding dependencies.
2. **No higher-kinded types**: Cannot abstract over `Result<_,_>` vs `Option<_>` generically. Use concrete types or computation expressions.
3. **.NET OO pressure**: C# interop pushes toward classes. Resist: use modules, records, and DUs as primary modeling tools.
4. **Forgetting Result.mapError**: When composing steps with different error types, unify with `Result.mapError` before `Result.bind`.Related Skills
nw-ux-web-patterns
Web UI design patterns for product owners. Load when designing web application interfaces, writing web-specific acceptance criteria, or evaluating responsive designs.
nw-ux-tui-patterns
Terminal UI and CLI design patterns for product owners. Load when designing command-line tools, interactive terminal applications, or writing CLI-specific acceptance criteria.
nw-ux-principles
Core UX principles for product owners. Load when evaluating interface designs, writing acceptance criteria with UX requirements, or reviewing wireframes and mockups.
nw-ux-emotional-design
Emotional design and delight patterns for product owners. Load when designing onboarding flows, empty states, first-run experiences, or evaluating the emotional quality of an interface.
nw-ux-desktop-patterns
Desktop application UI patterns for product owners. Load when designing native or cross-platform desktop applications, writing desktop-specific acceptance criteria, or evaluating panel layouts and keyboard workflows.
nw-user-story-mapping
User story mapping for backlog management and outcome-based prioritization. Load during Phase 2.5 (User Story Mapping) to produce story-map.md and prioritization.md.
nw-tr-review-criteria
Review dimensions and scoring for root cause analysis quality assessment
nw-tlaplus-verification
TLA+ formal verification for design correctness and PBT pipeline integration
nw-test-refactoring-catalog
Detailed refactoring mechanics with step-by-step procedures, and test code smell catalog with detection patterns and before/after examples
nw-test-organization-conventions
Test directory structure patterns by architecture style, language conventions, naming rules, and fixture placement. Decision tree for selecting test organization strategy.
nw-test-design-mandates
Four design mandates for acceptance tests - hexagonal boundary enforcement, business language abstraction, user journey completeness, walking skeleton strategy, and pure function extraction
nw-tdd-review-enforcement
Test design mandate enforcement, test budget validation, 5-phase TDD validation, and external validity checks for the software crafter reviewer