mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-13 18:00:35 +08:00
113 lines
2.8 KiB
Markdown
113 lines
2.8 KiB
Markdown
---
|
|
paths:
|
|
- "**/*.fs"
|
|
- "**/*.fsx"
|
|
---
|
|
# F# Coding Style
|
|
|
|
> This file extends [common/coding-style.md](../common/coding-style.md) with F#-specific content.
|
|
|
|
## Standards
|
|
|
|
- Follow standard F# conventions and leverage the type system for correctness
|
|
- Prefer immutability by default; use `mutable` only when justified by performance
|
|
- Keep modules focused and cohesive
|
|
|
|
## Types and Models
|
|
|
|
- Prefer discriminated unions for domain modeling over class hierarchies
|
|
- Use records for data with named fields
|
|
- Use single-case unions for type-safe wrappers around primitives
|
|
- Avoid classes unless interop or mutable state requires them
|
|
|
|
```fsharp
|
|
type EmailAddress = EmailAddress of string
|
|
|
|
type OrderStatus =
|
|
| Pending
|
|
| Confirmed of confirmedAt: DateTimeOffset
|
|
| Shipped of trackingNumber: string
|
|
| Cancelled of reason: string
|
|
|
|
type Order =
|
|
{ Id: Guid
|
|
CustomerId: string
|
|
Status: OrderStatus
|
|
Items: OrderItem list }
|
|
```
|
|
|
|
## Immutability
|
|
|
|
- Records are immutable by default; use `with` expressions for updates
|
|
- Prefer `list`, `map`, `set` over mutable collections
|
|
- Avoid `ref` cells and mutable fields in domain logic
|
|
|
|
```fsharp
|
|
let rename (profile: UserProfile) newName =
|
|
{ profile with Name = newName }
|
|
```
|
|
|
|
## Function Style
|
|
|
|
- Prefer small, composable functions over large methods
|
|
- Use the pipe operator `|>` to build readable data pipelines
|
|
- Prefer pattern matching over if/else chains
|
|
- Use `Option` instead of null; use `Result` for operations that can fail
|
|
|
|
```fsharp
|
|
let processOrder order =
|
|
order
|
|
|> validateItems
|
|
|> Result.bind calculateTotal
|
|
|> Result.map applyDiscount
|
|
|> Result.mapError OrderError
|
|
```
|
|
|
|
## Async and Error Handling
|
|
|
|
- Use `task { }` for interop with .NET async APIs
|
|
- Use `async { }` for F#-native async workflows
|
|
- Propagate `CancellationToken` through public async APIs
|
|
- Prefer `Result` and railway-oriented programming over exceptions for expected failures
|
|
|
|
```fsharp
|
|
let loadOrderAsync (orderId: Guid) (ct: CancellationToken) =
|
|
task {
|
|
let! order = repository.FindAsync(orderId, ct)
|
|
return
|
|
order
|
|
|> Option.defaultWith (fun () ->
|
|
failwith $"Order {orderId} was not found.")
|
|
}
|
|
```
|
|
|
|
## Formatting
|
|
|
|
- Use `fantomas` for automatic formatting
|
|
- Prefer significant whitespace; avoid unnecessary parentheses
|
|
- Remove unused `open` declarations
|
|
|
|
### Open Declaration Order
|
|
|
|
Group `open` statements into four sections separated by a blank line, each section sorted lexically within itself:
|
|
|
|
1. `System.*`
|
|
2. `Microsoft.*`
|
|
3. Third-party namespaces
|
|
4. First-party / project namespaces
|
|
|
|
```fsharp
|
|
open System
|
|
open System.Collections.Generic
|
|
open System.Threading.Tasks
|
|
|
|
open Microsoft.AspNetCore.Http
|
|
open Microsoft.Extensions.Logging
|
|
|
|
open FsCheck.Xunit
|
|
open Swensen.Unquote
|
|
|
|
open MyApp.Domain
|
|
open MyApp.Infrastructure
|
|
```
|