F# Programming Language Guide — Functional-First .NET Language
F# is a functional-first programming language on the .NET platform that combines the expressiveness of functional programming with the power and reach of the .NET ecosystem — enabling concise, correct, and performant code.
What You’ll Learn
- F# syntax and functional-first approach
- Immutability by default and type inference
- Discriminated unions and pattern matching
- Async workflows for asynchronous programming
- .NET interop and Visual Studio integration
Why It Matters
F# brings the rigor of functional programming to the .NET ecosystem — used for financial modeling, data science, web services, and cross-platform applications. DodaZIP uses F# for its compression algorithm configuration system where immutable data prevents subtle threading bugs. Financial institutions like Morgan Stanley and Credit Suisse use F# for trading systems that require both correctness and performance. F# runs anywhere .NET runs — Windows, Linux, macOS, Docker, and cloud — and integrates seamlessly with C# and .NET libraries.
Learning Path
flowchart LR
A[F# Basics<br/>You are here] --> B[Functions & Types]
B --> C[Pattern Matching & DU]
C --> D[Async & .NET Interop]
D --> E[Build a Real Application]
Your First F# Program
// hello.fsx
printfn "Hello, F#!"
// F# uses significant whitespace (like Python)
let name = "World"
printfn "Hello, %s!" namedotnet fsi hello.fsx
# Hello, F#!
# Hello, World!Immutability by Default
Variables in F# are immutable by default — a core functional principle.
// Immutable binding
let x = 10
// x <- 20 // ERROR: cannot reassign
// Mutable variable (explicit)
let mutable y = 10
y <- 20 // OK
// Shadowing (creating a new binding with same name)
let z = 5
let z = z + 3 // new z = 8, original z is unchanged
// Collection immutability
let numbers = [1; 2; 3; 4; 5]
let doubled = numbers |> List.map (fun n -> n * 2)
let filtered = numbers |> List.filter (fun n -> n % 2 = 0)
printfn "Original: %A" numbers // [1; 2; 3; 4; 5]
printfn "Doubled: %A" doubled // [2; 4; 6; 8; 10]
printfn "Evens: %A" filtered // [2; 4]Original: [1; 2; 3; 4; 5]
Doubled: [2; 4; 6; 8; 10]
Evens: [2; 4]Functions and Type Inference
F# uses type inference — the compiler deduces types from usage.
// Simple function
let add x y = x + y
// Function with type annotation
let multiply (x: int) (y: int) : int = x * y
// Lambda (anonymous function)
let square = fun x -> x * x
// Pipeline operator |>
let result = 5 |> square |> add 10
printfn "Result: %d" result // 35
// Function composition
let doubleThenAdd1 = (fun x -> x * 2) >> (fun x -> x + 1)
printfn "Composed: %d" (doubleThenAdd1 5) // 11
// Partial application
let add5 = add 5
printfn "Partial: %d" (add5 10) // 15
// Recursive function (needs rec keyword)
let rec factorial n =
if n <= 1 then 1
else n * factorial (n - 1)
printfn "Factorial 6: %d" (factorial 6) // 720Result: 35
Composed: 11
Partial: 15
Factorial 6: 720Discriminated Unions
Discriminated unions (DUs) model data that can take different forms — one of the most powerful F# features.
// Simple discriminated union
type PaymentMethod =
| Cash
| Card of string
| Crypto of address: string * amount: float
// Using the DU
let payment1 = Cash
let payment2 = Card "Visa-1234"
let payment3 = Crypto ("0xabc123", 0.05)
// Option type (built-in DU): Some | None
let safeDivide x y =
if y = 0 then None
else Some (x / y)
// Result type (built-in DU): Ok | Error
type DivisionError = DivisionByZero | NegativeInput
let safeDivide2 x y =
if y = 0 then Error DivisionByZero
elif x < 0 then Error NegativeInput
else Ok (x / y)
printfn "%A" (safeDivide 10 2) // Some 5
printfn "%A" (safeDivide 10 0) // NonePattern Matching
Pattern matching is F#’s Swiss Army knife — it works with DUs, lists, tuples, and more.
// Match on discriminated union
let describePayment payment =
match payment with
| Cash -> "Paying with cash"
| Card issuer -> sprintf "Paying with %s card" issuer
| Crypto (addr, amt) -> sprintf "Crypto: %f from %s" amt addr
printfn "%s" (describePayment (Card "Mastercard-5678"))
// Paying with Mastercard-5678 card
// Match on list patterns
let describeList lst =
match lst with
| [] -> "Empty"
| [x] -> sprintf "One item: %d" x
| [x; y] -> sprintf "Two items: %d, %d" x y
| head :: tail -> sprintf "Head: %d, Tail length: %d" head (tail.Length)
printfn "%s" (describeList [1; 2; 3]) // Head: 1, Tail length: 2
// Active patterns (custom pattern matching)
let (|Even|Odd|) n =
if n % 2 = 0 then Even else Odd
let describeNumber n =
match n with
| Even -> sprintf "%d is even" n
| Odd -> sprintf "%d is odd" n
printfn "%s" (describeNumber 42) // 42 is evenPaying with Mastercard-5678 card
Head: 1, Tail length: 2
42 is evenAsync Workflows
F# has built-in async support through computation expressions.
open System.Net.Http
open System.IO
// Async workflow
let fetchUrlAsync (url: string) = async {
use client = new HttpClient()
let! response = client.GetAsync(url) |> Async.AwaitTask
let! content = response.Content.ReadAsStringAsync() |> Async.AwaitTask
return content
}
// Run multiple async operations in parallel
let fetchAllAsync urls =
urls
|> List.map fetchUrlAsync
|> Async.Parallel
|> Async.RunSynchronously
// Async file processing
let processFileAsync (path: string) = async {
let! content = File.ReadAllTextAsync(path) |> Async.AwaitTask
let wordCount = content.Split(' ') |> Array.length
return (path, wordCount)
}
let files = ["file1.txt"; "file2.txt"; "file3.txt"]
let results = files |> List.map processFileAsync |> Async.Parallel |> Async.RunSynchronously
for (path, count) in results do
printfn "%s: %d words" path count.NET Interop
F# interoperates with C# and any .NET library seamlessly.
open System
open System.Collections.Generic
open System.Text.RegularExpressions
// Use .NET collections
let dict = Dictionary<string, int>()
dict.Add("one", 1)
dict.Add("two", 2)
dict.Add("three", 3)
// Use .NET regex
let pattern = @"\d+"
let regex = Regex(pattern)
let matches = regex.Matches("abc123def456")
for m in matches do
printfn "Found: %s" m.Value
// Found: 123
// Found: 456
// Call C# libraries
open System.Net
let client = WebClient()
let html = client.DownloadString("https://example.com")
printfn "Downloaded %d characters" html.Length
// Use System.Linq (via F# Seq module)
let numbers = [1..100]
let evens = numbers |> Seq.filter (fun n -> n % 2 = 0) |> Seq.toList
let squared = numbers |> Seq.map (fun n -> n * n) |> Seq.take 5 |> Seq.toListCommon Mistakes
1. Forgetting rec for recursive functions
F# assumes functions can’t call themselves unless you add rec. let rec factorial n = ... — without rec, you get a reference error.
2. Confusing = with <-
= creates an immutable binding. <- assigns to a mutable value. let x = 5 vs x <- 10 (requires let mutable x = 5 first).
3. Not handling all DU cases in match
match payment with
| Cash -> ...
// ERROR: incomplete match — Card and Crypto not handledF# enforces exhaustive matching. Use _ as catch-all if needed.
4. Mixing up list types
[1; 2; 3] is an F# list (linked list). [|1; 2; 3|] is an array. ResizeArray<int>() is a mutable .NET List. They have different performance characteristics and APIs.
5. Incorrect indentation
F# uses significant whitespace. Wrong indentation causes unexpected parsing errors. Use 4 spaces consistently.
6. Forgetting ! for async
let! within async { } awaits an async operation inside a computation expression. Without it, you get the async object, not the result. Don’t confuse let! with regular let.
Practice Questions
What makes F# immutable by default? All bindings are immutable unless explicitly marked
mutable. Collections are immutable by default (lists, maps, sets). This eliminates entire categories of bugs from unexpected mutation.What are discriminated unions? Types that can take one of several named cases, each optionally carrying data. Like enums with payloads. They enable exhaustive pattern matching and precise domain modeling.
How does F# pattern matching differ from C# switch? F# pattern matching is more powerful: exhaustive (compiler checks), works with DUs, lists, tuples, active patterns, and supports guards and destructuring. C# switch has become more powerful but is still more limited.
What is the pipe operator
|>?x |> fpassesxas the last argument tof. It chains operations left-to-right:data |> process |> analyze |> display. Makes code read in the order of operations.How does F# handle async differently from C#? F# uses computation expressions:
async { let! result = fetchAsync() ... }. Tasks are explicit and composable. F# async is a cold model (starts only when run). C# async/await uses hot tasks (start as soon as created).
Challenge: Write an F# script that reads a CSV file, parses it into a list of records using a discriminated union for error handling, processes the data (filter, sort, aggregate), and writes the result to a new CSV file.
Mini Project — Markdown to HTML Converter
open System.Text.RegularExpressions
type InlineElement =
| Text of string
| Bold of string
| Italic of string
| Code of string
| Link of text: string * url: string
type BlockElement =
| Heading of level: int * content: string
| Paragraph of elements: InlineElement list
| CodeBlock of language: string * code: string
| UnorderedList of items: string list
| OrderedList of items: string list
let parseInline (text: string) : InlineElement list =
let regex = Regex(@"(?s)(\*\*(.+?)\*\*)|(\*(.+?)\*)|(`(.+?)`)|\[(.+?)\]\((.+?)\)")
let matches = regex.Matches(text)
if matches.Count = 0 then [Text text]
else
[ for m in matches do
if m.Groups.[2].Success then yield Bold m.Groups.[2].Value
elif m.Groups.[4].Success then yield Italic m.Groups.[4].Value
elif m.Groups.[6].Success then yield Code m.Groups.[6].Value
elif m.Groups.[8].Success then yield Link (m.Groups.[8].Value, m.Groups.[9].Value) ]
let renderInline element =
match element with
| Text t -> t
| Bold t -> sprintf "<strong>%s</strong>" t
| Italic t -> sprintf "<em>%s</em>" t
| Code c -> sprintf "<code>%s</code>" c
| Link (text, url) -> sprintf "<a href=\"%s\">%s</a>" url text
let renderBlock block =
match block with
| Heading (lvl, content) ->
sprintf "<h%d>%s</h%d>" lvl content lvl
| Paragraph elements ->
let inner = elements |> List.map renderInline |> String.concat ""
sprintf "<p>%s</p>" inner
| CodeBlock (lang, code) ->
let langAttr = if lang <> "" then sprintf " class=\"language-%s\"" lang else ""
sprintf "<pre><code%s>%s</code></pre>" langAttr code
| UnorderedList items ->
let lis = items |> List.map (sprintf "<li>%s</li>") |> String.concat "\n"
sprintf "<ul>\n%s\n</ul>" lis
| OrderedList items ->
let lis = items |> List.map (sprintf "<li>%s</li>") |> String.concat "\n"
sprintf "<ol>\n%s\n</ol>" lis
let parseMarkdown (md: string) : BlockElement list =
let lines = md.Split('\n') |> Array.toList
let rec parseLines lines acc =
match lines with
| [] -> acc |> List.rev
| line :: rest when line.StartsWith("# ") ->
parseLines rest (Heading (1, line.Substring(2)) :: acc)
| line :: rest when line.StartsWith("## ") ->
parseLines rest (Heading (2, line.Substring(3)) :: acc)
| line :: rest when line.StartsWith("- ") ->
let items = line.Substring(2) :: (rest |> List.takeWhile (fun l -> l.StartsWith("- ")) |> List.map (fun l -> l.Substring(2)))
let remaining = rest |> List.skipWhile (fun l -> l.StartsWith("- "))
parseLines remaining (UnorderedList items :: acc)
| line :: rest when line = "" ->
parseLines rest acc
| line :: rest ->
parseLines rest (Paragraph (parseInline line) :: acc)
parseLines lines []
let convertMarkdownToHtml (md: string) =
let blocks = parseMarkdown md
let body = blocks |> List.map renderBlock |> String.concat "\n\n"
sprintf "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<title>Converted Markdown</title>\n</head>\n<body>\n%s\n</body>\n</html>" body
// Example usage
let markdown = """
# Hello F#
This is **bold** and *italic* text with `inline code`.
- Item one
- Item two
- Item three
"""
let html = convertMarkdownToHtml markdown
printfn "%s" html<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Converted Markdown</title>
</head>
<body>
<h1>Hello F#</h1>
<p>This is <strong>bold</strong> and <em>italic</em> text with <code>inline code</code>.</p>
<ul>
<li>Item one</li>
<li>Item two</li>
<li>Item three</li>
</ul>
</body>
</html>FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro