Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@
"source": "./plugins/dotnet-aspnetcore",
"description": "ASP.NET Core web development skills including middleware, endpoints, real-time communication, and API patterns."
},
{
"name": "dotnet-fsharp",
"source": "./plugins/dotnet-fsharp",
"description": "Idiomatic F# coding skills: functional-first design, domain modeling, error handling, scripting, and .NET interop."
},
{
"name": "dotnet-blazor",
"source": "./plugins/dotnet-blazor",
Expand Down
5 changes: 5 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@
"name": "dotnet11",
"source": "./plugins/dotnet11",
"description": "Skills for new .NET 11 APIs and language features."
},
{
"name": "dotnet-fsharp",
"source": "./plugins/dotnet-fsharp",
"description": "Idiomatic F# coding skills: functional-first design, domain modeling, error handling, scripting, and .NET interop."
}
]
}
5 changes: 5 additions & 0 deletions .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@
"name": "dotnet11",
"source": "./plugins/dotnet11",
"description": "Skills for new .NET 11 APIs and language features."
},
{
"name": "dotnet-fsharp",
"source": "./plugins/dotnet-fsharp",
"description": "Idiomatic F# coding skills: functional-first design, domain modeling, error handling, scripting, and .NET interop."
}
]
}
5 changes: 5 additions & 0 deletions .github/plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@
"name": "dotnet11",
"source": "./plugins/dotnet11",
"description": "Skills for new .NET 11 APIs and language features."
},
{
"name": "dotnet-fsharp",
"source": "./plugins/dotnet-fsharp",
"description": "Idiomatic F# coding skills: functional-first design, domain modeling, error handling, scripting, and .NET interop."
}
]
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ This repository contains the .NET team's curated set of core skills and custom a
| [dotnet-aspnetcore](plugins/dotnet-aspnetcore/) | ASP.NET Core web development skills including middleware, endpoints, real-time communication, and API patterns. |
| [dotnet-blazor](plugins/dotnet-blazor/) | Skills for Blazor development: component authoring, interactivity, and web application patterns. |
| [dotnet11](plugins/dotnet11/) | Skills for new .NET 11 APIs and language features. |
| [dotnet-fsharp](plugins/dotnet-fsharp/) | Idiomatic F# coding skills: functional-first design, domain modeling, error handling, scripting, and .NET interop. |

## Installation

Expand Down
6 changes: 6 additions & 0 deletions eng/known-domains.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ github.com/username
# Test smell research
testsmells.org

# F# ecosystem
fsprojects.github.io
fscheck.github.io
github.com/fsprojects/fantomas
github.com/haf/expecto

# Community
github.com/Youssef1313/Combinatorial.MSTest
ollama.com
Expand Down
6 changes: 6 additions & 0 deletions plugins/dotnet-fsharp/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "dotnet-fsharp",
"version": "0.1.0",
"description": "Idiomatic F# coding skills: functional-first design, domain modeling, error handling, scripting, and .NET interop.",
"skills": ["./skills/"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
name: authoring-computation-expressions
description: "Author custom F# computation expression builders (the result {}, option {}, async {} style of block). Use when you want a let!/return DSL for a wrapper type such as Result or Option, to remove repetitive bind/map chains, or to build a small workflow builder. Covers the builder class, the core members (Bind, Return, ReturnFrom, Zero, Combine, Delay, For, While, Using, TryWith), and a minimal result/option builder. Do not use for merely consuming existing computation expressions, or where a plain pipeline of bind/map is already clear."
license: MIT
---

# Authoring F# Computation Expressions

## Purpose

A computation expression (CE) is a builder object that gives a type a `let!`/`return` block
syntax. Authoring one lets you replace repetitive `bind`/`map` chains with readable sequential
code for your own wrapper types.

## When to Use

- You repeatedly chain `Result.bind` / `Option.bind` and want `let!`/`return` instead
- You have a custom wrapper/effect type that would benefit from block syntax
- You are building a small internal DSL (parsers, builders, pipelines)

## When Not to Use

- You only need to **use** an existing CE (`async`, `task`, `result`) - just use it
- A single `bind`/`map` pipeline is already clear; a CE adds indirection
- A library already provides the builder (e.g. FsToolkit.ErrorHandling's `result`/`validation`)

## How a CE works

`builder { ... }` desugars to method calls on a builder instance:

| Syntax | Builder member |
|--------|----------------|
| `let! x = e in rest` | `Bind(e, fun x -> rest)` |
| `return x` | `Return(x)` |
| `return! e` | `ReturnFrom(e)` |
| (empty / `if` with no else) | `Zero()` |
| two statements in sequence | `Combine(a, b)` |
| delayed evaluation | `Delay(fun () -> ...)` |
| `for x in xs do ...` | `For(xs, body)` |
| `while cond do ...` | `While(guard, body)` |
| `use x = e in ...` | `Using(e, body)` |
| `try ... with` | `TryWith(body, handler)` |

You only implement the members your block actually uses.

## A minimal result builder

```fsharp
type ResultBuilder() =
member _.Bind(result, f) = Result.bind f result
member _.Return(value) = Ok value
member _.ReturnFrom(result) = result
member _.Zero() = Ok ()

let result = ResultBuilder()

// usage
let compute a b =
result {
let! x = if a > 0 then Ok a else Error "a must be positive"
let! y = if b > 0 then Ok b else Error "b must be positive"
return x + y
}
```

`let!` chains via `Bind`; the first `Error` short-circuits the rest of the block.

## A minimal option builder

```fsharp
type OptionBuilder() =
member _.Bind(opt, f) = Option.bind f opt
member _.Return(value) = Some value
member _.ReturnFrom(opt) = opt

let option = OptionBuilder()
```

## Adding more members

- Add `Zero` to allow `if cond then return! x` with no `else`.
- Add `Combine` + `Delay` to allow multiple statements / early constructs.
- Add `For`/`While` only if the block needs loops.
- Add `Using`/`TryWith`/`TryFinally` for resource and exception handling inside the block.

Implement incrementally: the compiler error names the missing member when a block needs one.

## Workflow

1. Identify the wrapper type and the repetitive `bind`/`map` chain.
2. Write a builder class with `Bind` and `Return` (and `ReturnFrom`/`Zero` as needed).
3. Instantiate it as a lowercase value (`let result = ResultBuilder()`).
4. Rewrite the chain as a `builder { ... }` block.
5. Add further members only when a compiler error asks for one.
6. Verify with `dotnet fsi`.

## Validation

- [ ] Builder implements at least `Bind` and `Return` with correct types
- [ ] The CE block compiles and behaves like the underlying `bind`/`map` chain
- [ ] Short-circuiting / sequencing matches the wrapper's semantics
- [ ] No CE authored where an existing one (or a plain pipeline) would do

## Common Pitfalls

| Pitfall | Correction |
|---------|------------|
| Re-implementing `result`/`validation` that a library already ships | Use FsToolkit.ErrorHandling instead |
| Wrong `Bind` signature | `Bind: M<'a> * ('a -> M<'b>) -> M<'b>` for the wrapper `M` |
| Block needs `Zero`/`Combine` but they are missing | Add the member the compiler error names |
| Authoring a CE for a one-off two-step chain | Just use `bind`/`map` directly |

## More info

- Computation expressions: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions
83 changes: 83 additions & 0 deletions plugins/dotnet-fsharp/skills/convert-csharp-to-fsharp/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
name: convert-csharp-to-fsharp
description: "Translate C# code into idiomatic F#, not a literal line-by-line port. Use when porting a C# class, method, or file to F#, or when an F# translation needs to be made idiomatic. Produces F# that uses records/DUs, Option over null, Result over exceptions, pattern matching, and pipelines. Composes the writing-idiomatic-fsharp, fsharp-domain-modeling, and fsharp-error-handling skills, plus design-fsharp-for-dotnet-interop when the result must stay C#-consumable. Do not use for fresh F# design with no C# source (use writing-idiomatic-fsharp / fsharp-domain-modeling)."
license: MIT
---

# Converting C# to F#

## Purpose

Port C# to F# that an F# developer would actually write - leveraging records, discriminated
unions, `Option`, `Result`, pattern matching, and pipelines - rather than transliterating C#
syntax into `.fs`.

## When to Use

- Porting a C# class, method, or file to F#
- Cleaning up an F# translation that still reads like C#
- Migrating a component to F# while keeping (or dropping) C# consumability

## When Not to Use

- Designing fresh F# with no C# source - use `writing-idiomatic-fsharp` and
`fsharp-domain-modeling` directly

## How C# constructs map to idiomatic F#

| C# | Idiomatic F# |
|----|--------------|
| POCO / DTO class with get/set | `record` (immutable; `with` for updates) |
| `enum` | discriminated union (or enum if interop needs it) |
| class hierarchy / `abstract` + subclasses for variants | discriminated union + `match` |
| `null` / nullable reference | `Option` (`Some`/`None`) |
| `throw` / `try-catch` for expected failures | `Result` + `result { }` (see `fsharp-error-handling`) |
| `if/else if` chains, `switch` | `match` |
| `for`/`foreach` building a collection | `List`/`Seq` `map`/`filter`/`fold` |
| `interface` with methods | `interface` (kept) or a record/DU of functions where simpler |
| `static` helper class | a `module` of functions |
| LINQ (`Where`/`Select`/`Aggregate`) | `List.filter`/`List.map`/`List.fold` pipelines |
| `async`/`await`, `Task<T>` | `task { }` / `async { }` (see `fsharp-async-and-tasks`) |

## Workflow

1. **Understand the C#** - identify data types, control flow, error handling, and async.
2. **Model the data first** (`fsharp-domain-modeling`): DTOs to records, enums/variants to DUs,
nullable to `Option`; add smart constructors for validated values.
3. **Translate behavior** (`writing-idiomatic-fsharp`): `switch`/`if` to `match`, loops to
collection functions, nested calls to pipelines, LINQ to `List`/`Seq` functions.
4. **Convert error handling** (`fsharp-error-handling`): expected exceptions to `Result`; wrap
genuinely throwing .NET calls at the boundary.
5. **Convert async** (`fsharp-async-and-tasks`): `Task`/`await` to `task { }`/`async { }`; no
`.Result` blocking.
6. **Preserve interop if required** (`design-fsharp-for-dotnet-interop`): if C# must still
consume the result, keep the public surface C#-friendly.
7. **Mind file order**: in a project, place definitions before use in `.fsproj`
(`fsharp-project-organization`).
8. **Verify**: build/run and, where practical, port or run the existing tests to confirm
behavior is preserved.

## Validation

- [ ] DTO classes became immutable records; variant hierarchies/enums became DUs
- [ ] `null` replaced with `Option`; expected exceptions replaced with `Result`
- [ ] `switch`/`if` chains became `match`; loops became collection functions; LINQ became
pipelines
- [ ] `async`/`await` became `task`/`async` with no blocking `.Result`
- [ ] Behavior preserved (tests pass or output matches the C# original)
- [ ] If C#-consumable: public surface still follows the interop rules

## Common Pitfalls

| Pitfall | Correction |
|---------|------------|
| Line-by-line transliteration | Re-model the data and control flow idiomatically first |
| Keeping `null` and `try/catch` as-is | Map to `Option`/`Result` |
| Porting mutable classes verbatim | Prefer records/DUs; isolate mutation if genuinely needed |
| Ignoring `.fsproj` file order | Order definitions before use; entry point last |
| Breaking C# callers during a partial migration | Apply `design-fsharp-for-dotnet-interop` to the public surface |

## More info

- F# style guide: https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/
- F# for C#/OO developers: https://learn.microsoft.com/en-us/dotnet/fsharp/
Loading