Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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