| name | swe-programming-fsharp |
| description | F# coding standards from authoritative docs/explanation/software-engineering/programming-languages/f-sharp/ documentation |
F# Coding Standards
Purpose
Progressive disclosure of F# coding standards for agents writing F# code.
Authoritative Source: docs/explanation/software-engineering/programming-languages/f-sharp/README.md
Usage: Auto-loaded for agents when writing F# code. Provides quick reference to idioms, best practices, and antipatterns.
Prerequisite Knowledge
IMPORTANT: This skill provides OSE Platform-specific style guides, not educational tutorials.
You MUST understand F# fundamentals before using these standards. Complete the AyoKoding F# learning path first:
- F# Learning Path - Initial setup, language overview, quick start guide (0-95% language coverage)
- F# By Example - 75+ annotated code examples (beginner to advanced patterns)
What this skill covers: OSE Platform naming conventions, framework choices, repository-specific patterns, how to apply F# knowledge in THIS codebase.
What this skill does NOT cover: F# syntax, language fundamentals, generic patterns (those are in ayokoding-web).
See: Programming Language Documentation Separation for content separation rules.
Quick Standards Reference
Naming Conventions
Modules/Types/DUs: PascalCase - ZakatCalculator, MurabahaContract, PaymentResult
Functions/Values: camelCase - calculateZakat, totalAmount, validateContract
DU Cases: PascalCase - Due, BelowNisab, ValidationError
Predicate functions: isValid, hasPayments (boolean-returning functions prefixed with is/has)
Discriminated Unions for Domain Modeling
// CORRECT: DU for domain states (exhaustive)
type ZakatResult =
| Due of amount: decimal
| BelowNisab
| ValidationError of message: string
// CORRECT: Exhaustive pattern matching (compiler enforced)
let handleResult result =
match result with
| Due amount -> sprintf "Zakat due: %M" amount
| BelowNisab -> "Below nisab threshold"
| ValidationError msg -> sprintf "Error: %s" msg
Railway-Oriented Programming
// CORRECT: Result type for error handling
let calculateZakat (wealth: decimal) (nisab: decimal) : Result<decimal, string> =
if wealth < 0m then
Error "Wealth cannot be negative"
elif wealth >= nisab then
Ok (wealth * 0.025m)
else
Ok 0m
// CORRECT: Computation expression for chaining
let processPayment (wealth: decimal) (nisab: decimal) =
result {
let! zakatAmount = calculateZakat wealth nisab
let! validated = validateAmount zakatAmount
return! saveZakat validated
}
Pipeline Operator
// CORRECT: Use |> for readable pipelines
let totalZakat =
wealthAmounts
|> List.filter (fun w -> w >= nisabThreshold)
|> List.map (fun w -> w * 0.025m)
|> List.sum
// CORRECT: Function composition with >>
let calculateAndValidate = calculateZakat >> validateZakat
Records (Immutable by Default)
// CORRECT: Record type for value objects
type ZakatCalculation = {
Wealth: decimal
Nisab: decimal
Amount: decimal
CalculationDate: DateOnly
}
// CORRECT: Record copy expression (non-destructive update)
let updated = { calculation with Amount = newAmount }
Async Workflows
// CORRECT: F# async computation expression
let calculateAsync wealth nisab = async {
let! nisabValue = repository.GetNisabAsync()
let result = calculateZakat wealth nisabValue
return result
}
// CORRECT: Running async
let result = calculateAsync 10000m 5000m |> Async.RunSynchronously
// CORRECT: Task interop
let taskAsync = calculateAsync 10000m 5000m |> Async.StartAsTask
Fantomas Formatting (MANDATORY)
// CORRECT: Fantomas-formatted code
let calculate (wealth: decimal) (nisab: decimal) =
if wealth >= nisab then
wealth * 0.025m
else
0m
// Run: dotnet fantomas . (formats all F# files)
// Pre-commit: fantomas --check . (fails if not formatted)
Testing with Expecto
open Expecto
let zakatTests =
testList "ZakatCalculator" [
test "calculates 2.5% when above nisab" {
let result = calculateZakat 10000m 5000m
Expect.equal result (Ok 250m) "Should return 2.5% of wealth"
}
test "returns 0 when below nisab" {
let result = calculateZakat 1000m 5000m
Expect.equal result (Ok 0m) "Should return 0 below nisab"
}
]
[<EntryPoint>]
let main args = runTestsWithCLIArgs [] args zakatTests
Property-Based Testing with FsCheck
open FsCheck
let zakatProperties =
testList "ZakatCalculator properties" [
testProperty "zakat is always non-negative" <| fun (wealth: decimal) ->
let nisab = 5000m
match calculateZakat (abs wealth) nisab with
| Ok amount -> amount >= 0m
| Error _ -> true
testProperty "zakat is exactly 2.5% when above nisab" <| fun (wealth: decimal) ->
wealth > 5000m ==>
(calculateZakat wealth 5000m = Ok (wealth * 0.025m))
]
Comprehensive Documentation
Authoritative Index: docs/explanation/software-engineering/programming-languages/f-sharp/README.md
Mandatory Standards (All F# Code MUST Follow)
- Coding Standards - F# naming conventions, module organization, pipeline idioms
- Testing Standards - Expecto, FsCheck property-based testing, AltCover coverage
- Code Quality Standards - Fantomas, FSharpLint, exhaustive pattern matching
- Build Configuration - .fsproj file order, dotnet CLI, Nx integration
Context-Specific Standards (Apply When Relevant)
- Error Handling Standards - Result type, railway-oriented programming, computation expressions
- Concurrency Standards - Async workflows, MailboxProcessor, Task interop
- Functional Programming Standards - Computation expressions, monads, applicatives
- Type Safety Standards - DUs, units of measure, phantom types
- Performance Standards - Tail recursion, sequences, lazy evaluation
- Security Standards - Type-driven validation, Giraffe authentication
- API Standards - Giraffe HttpHandler composition, Saturn routing
- DDD Standards - DU-based domain modeling, making illegal states unrepresentable
Related Skills
- docs-applying-content-quality
- repo-practicing-trunk-based-development
References