LEARN FSHARP DEEP DIVE
Learn F#: From Imperative to Functional Master
Goal: To master the F# programming language and the functional way of thinking. You will learn to build robust, concise, and elegant applications by leveraging F#’s strong type system, immutability, and powerful data-processing capabilities.
Why Learn F#?
F# offers a unique blend of features: it’s a functional-first language with a powerful type system, but it also runs on the .NET platform, giving you seamless access to the vast ecosystem of C# libraries and tools. It’s designed to write correct, readable, and concurrent code, making it an excellent choice for data-rich applications, complex domain modeling, and robust backend services.
After completing these projects, you will:
- Think in terms of data transformations and pipelines, not just sequences of statements.
- Model complex business domains with precision and safety using records and discriminated unions.
- Write asynchronous and concurrent code that is easy to read and reason about.
- Appreciate the power of immutability and a type system that eliminates
nullreference errors. - Be able to build and contribute to modern .NET applications with a functional-first mindset.
Core Concept Analysis
F# encourages a different style of programming. Understanding these core concepts is key.
1. Immutability and the Pipe Operator |>
By default, all values in F# are immutable. You don’t change things; you create new things based on transformations of the old. The pipe operator |> is the most idiomatic way to chain these transformations, creating a readable data pipeline.
// Instead of this nested, hard-to-read style:
let result = sort(map(filter(data)))
// You write this clean, top-to-bottom pipeline:
let result =
data
|> List.filter (fun x -> x > 10)
|> List.map (fun x -> x * 2)
|> List.sort
2. Discriminated Unions and Pattern Matching
A Discriminated Union (DU) lets you define a type that can be one of several distinct cases. Pattern matching then lets you write code that safely and exhaustively handles every possible case. This is arguably F#’s most powerful feature for writing correct code.
// Define all possible states for a network request
type WebRequestState =
| Idle
| InProgress of float // % complete
| Succeeded of string // The content
| Failed of int * string // Status code and error message
// Pattern match to handle every case. The compiler warns you if you miss one!
let render state =
match state with
| Idle -> "Please start a request."
| InProgress p -> $"Loading... ({p}%)"
| Succeeded content -> $"Success! Content: {content}"
| Failed (code, msg) -> $"Error {code}: {msg}"
3. Record Types
Records are simple, immutable aggregates of named values. They are perfect for modeling data entities. They come with automatic equality, printing, and copying capabilities.
type Person = {
FirstName : string
LastName : string
Age : int
}
let alice = { FirstName = "Alice"; LastName = "Smith"; Age = 30 }
// Create a *new* record for "updated" data
let olderAlice = { alice with Age = 31 }
4. Handling Absence with the Option Type
F# avoids null reference exceptions by using the Option type. A function that might not be able to return a value will return an Option, which can either be Some value or None. This forces you to explicitly handle the “not found” case at compile time.
let findById id people = List.tryFind (fun p -> p.Id = id) people
match findById 123 people with
| Some person -> $"Found {person.Name}"
| None -> "Person not found."
Project List
These projects will help you practice and internalize the F# way of thinking, starting simple and moving to more complex, real-world scenarios. You will need the .NET SDK installed to get started.
Project 1: Data Log Processor
- File: LEARN_FSHARP_DEEP_DIVE.md
- Main Programming Language: F#
- Alternative Programming Languages: Python, Go
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 1: Beginner
- Knowledge Area: Data Processing / Functional Pipelines
- Software or Tool: .NET SDK
- Main Book: “Stylish F#” by Kit Eason
What you’ll build: A command-line tool that reads a file of structured logs (e.g., CSV or JSON), filters them based on criteria (e.g., only “ERROR” level logs), transforms the data (e.g., extracts a message), and prints a summary.
Why it teaches F#: This is the quintessential F# project. It’s a data transformation pipeline from start to finish. It will force you to use the core functional concepts: creating a pipeline with |>, and using List.map, List.filter, List.choose, and other list-processing functions.
Core challenges you’ll face:
- Reading file contents → maps to using .NET libraries like
System.IO.File - Parsing each line into a data record → maps to defining a
Recordtype and writing a parsing function - Filtering out irrelevant data → maps to using
List.filterwith a predicate function - Transforming data into a desired output format → maps to using
List.map
Key Concepts:
- Pipelines (
|>): The foundation of idiomatic F#. - List-processing functions:
map,filter,choose,fold. - Record Types: For modeling your log entries.
OptionType: Your parsing function should return anOptionto handle lines that don’t parse correctly.List.chooseis perfect for this.
Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic knowledge of any programming language.
Real world outcome: A tool that can process a log file and produce a clean report.
$ cat logs.txt
INFO,2023-10-01,User logged in
ERROR,2023-10-01,Database connection failed
WARN,2023-10-02,Disk space low
$ dotnet run logs.txt
Processing...
Found 1 error(s):
- Database connection failed
Implementation Hints:
- Define a record type:
type LogEntry = { Level: string; Timestamp: System.DateTime; Message: string }. - Create a
parseLinefunction:let parseLine (line: string) : LogEntry option = .... If parsing fails, returnNone. - Your main function will be a pipeline:
let processLogs filePath = File.ReadAllLines filePath |> Array.toList |> List.choose parseLine // Use choose to parse and filter out Nones in one step |> List.filter (fun entry -> entry.Level = "ERROR") |> List.map (fun entry -> entry.Message) // Now you have a list of strings (the error messages) to print
Learning milestones:
- You can read a file and parse it into a list of records → You understand basic F# syntax and types.
- Your code is structured as a single pipeline using
|>→ You are thinking functionally. - You use
List.chooseto handle parsing errors gracefully → You are using theOptiontype correctly. - You find yourself looking at problems as a series of data transformations → The F# mindset is clicking.
Project 2: Modeling a Board Game’s Rules
- File: LEARN_FSHARP_DEEP_DIVE.md
- Main Programming Language: F#
- Alternative Programming Languages: Rust, Haskell, Swift
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Domain Modeling / Type Systems / Pattern Matching
- Software or Tool: .NET SDK
- Main Book: “Domain Modeling Made Functional” by Scott Wlaschin
What you’ll build: The core, non-UI logic for a turn-based game like Tic-Tac-Toe, Checkers, or even a simplified version of Chess. You will model the game state, player actions, and game rules using F#’s powerful type system.
Why it teaches F#: This project is the perfect showcase for Discriminated Unions and pattern matching. You can model all possible states and actions in your type system, and the F# compiler will force you to handle every single case, making it impossible to have bugs like an invalid game state.
Core challenges you’ll face:
- Modeling the game state → maps to using DUs for states like
Playing | GameOver of winner - Modeling player moves → maps to using DUs or records to represent valid actions
- Implementing the game logic → maps to a core
updatefunction that uses pattern matching on the current state and the incoming move - Enforcing game rules via the type system → maps to making invalid states unrepresentable
Key Concepts:
- Discriminated Unions: For modeling “this OR that” scenarios.
- Pattern Matching: For safely deconstructing and acting on DUs.
- Immutability: Game state is never modified. The
updatefunction takes the old state and a move, and returns a new state.
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 1.
Real world outcome: A library representing the complete, verifiable rule-set of a game, which could then be used by any UI (console, web, etc.).
// Your types
type Player = X | O
type GameState = InProgress of Board | Win of Player | Draw
// Your core logic function
let makeMove (state: GameState) (move: Move) : GameState =
match state with
| Win _ | Draw -> state // Game is over, no more moves
| InProgress board ->
if isLegal move board then
let newBoard = applyMove move board
match checkForWinner newBoard with
| Some player -> Win player
| None -> if isBoardFull newBoard then Draw else InProgress newBoard
else
state // Illegal move, return original state
Implementation Hints:
- Start with your types. For Tic-Tac-Toe, a
Boardcould be aList<List<Option<Player>>>. AMovecould be a record{ Row: int; Col: int; Player: Player }. - Your core function will be
let update state move = .... This function will contain a single largematchexpression. - The beauty of this approach is that the compiler helps you write correct code. If you add a new
GameStatelikePaused, the compiler will give you an error everywhere you have amatchexpression that doesn’t handlePaused.
Learning milestones:
- You have modeled your game’s states and actions with DUs → You understand type-driven design.
- Your main game logic is a single, clean pattern matching expression → You are leveraging F#’s core strengths.
- It is impossible for your types to represent an invalid state → You have achieved “making illegal states unrepresentable.”
- You find yourself modeling real-world problems with DUs → You’ve internalized one of the most powerful concepts in modern programming.
Project 3: Concurrent Web Scraper
- File: LEARN_FSHARP_DEEP_DIVE.md
- Main Programming Language: F#
- Alternative Programming Languages: Go, Rust
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Concurrency / Asynchronous Programming / Actors
- Software or Tool: .NET SDK,
HtmlAgilityPacklibrary - Main Book: “Expert F# 4.0” by Don Syme, Adam Granicz, and Antonio Cisternino
What you’ll build: An application that crawls a website, starting from a single URL. It will asynchronously download the page’s HTML, parse it for new links, and concurrently process those new links, while keeping track of which pages have already been visited to avoid getting stuck in loops.
Why it teaches F#: This is the perfect project for learning F#’s approach to concurrency and asynchronicity. You will use async workflows to handle I/O without blocking threads, and a MailboxProcessor (an actor-like agent) to safely manage the shared state (the set of visited URLs) without needing locks or mutexes.
Core challenges you’ll face:
- Making asynchronous HTTP requests → maps to using
asyncworkflows and libraries likeHttpClient - Parsing HTML → maps to using a .NET library like
HtmlAgilityPackfrom F# - Managing shared state concurrently → maps to using a
MailboxProcessorto serialize access to the visited set and URL queue - Controlling the degree of parallelism → maps to using
Async.Paralleland limiting how many tasks run at once
Key Concepts:
asyncWorkflows: F#’s computation expression for asynchronous operations.MailboxProcessor: F#’s built-in, lightweight actor model for safe concurrent state management.- .NET Interoperability: Using existing C#/.NET libraries from F#.
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Solid understanding of F# basics.
Real world outcome: A console app that crawls a website and prints the titles of the pages it finds.
$ dotnet run https://www.adacore.com
Crawling https://www.adacore.com... Found title: AdaCore - Secure Software...
Found link: /company
Found link: /sparkpro
Crawling https://www.adacore.com/company... Found title: About Us...
...
Crawl complete. Visited 57 pages.
Implementation Hints:
- Your shared state (visited URLs, URLs to crawl) should be managed by a
MailboxProcessor. Define messages for your agent liketype AgentMsg = AddUrl of string | GetNextUrl of AsyncReplyChannel<string option>. - The main loop of your
MailboxProcessorwillReceiveand pattern match on these messages to update its internal state safely. - Your crawling logic will be an
asyncworkflow:let rec crawl url = async { // 1. Download HTML asynchronously let! html = downloadAsync url // 2. Parse HTML for new links let newLinks = parseLinks html // 3. Post new, unvisited links to the MailboxProcessor agent for link in newLinks do agent.Post(AddUrl link) // 4. Get the next URL to crawl from the agent let! nextUrl = agent.PostAndAsyncReply(GetNextUrl) // 5. Recurse or finish match nextUrl with | Some u -> return! crawl u | None -> () // Done } - You can start multiple
crawlworkflows in parallel usingAsync.ParallelandAsync.Start.
Learning milestones:
- You can download a single webpage asynchronously → You understand basic
asyncworkflows. - Your
MailboxProcessorcorrectly manages the visited URL set → You can write a basic agent for safe concurrent state. - Your crawler can process multiple pages in parallel without race conditions → You have built a non-trivial, safe, concurrent application.
- You default to using agents for shared mutable state instead of locks → You are proficient in F# concurrency.
Project 4: A Simple Web API with Giraffe
- File: LEARN_FSHARP_DEEP_DIVE.md
- Main Programming Language: F#
- Alternative Programming Languages: N/A
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Web Development / Functional Composition
- Software or Tool: .NET SDK, Giraffe library
- Main Book: “Get Programming with F#” by Isaac Abraham
What you’ll build: A minimal, fast, and functional REST API for a simple resource, like a To-Do list. It will support GET, POST, and DELETE requests to list, add, and remove items.
Why it teaches F#: This project shows how F#’s functional composition applies to a real-world domain like web development. The Giraffe framework is a set of functions that you compose together to create a web application. There are no classes, no inheritance, and no magic—just functions that take a HttpContext and return a HttpContext.
Core challenges you’ll face:
- Setting up an ASP.NET Core project with F# → maps to using the .NET CLI templates
- Composing HTTP handlers → maps to using Giraffe’s combinators like
>=>(compose) andchoose - Handling different HTTP verbs (GET, POST) → maps to using Giraffe’s verb-specific handlers
- Serializing F# records to and from JSON → maps to defining your data types and using a JSON library
Key Concepts:
- Functional Composition: Building complex functions by chaining together simpler ones.
- HTTP Handlers as Functions: The core concept of Giraffe.
- Immutability in a Web Context: Treating the
HttpContextas an immutable value that flows through the handler pipeline.
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Understanding of basic HTTP concepts.
Real world outcome:
A running web server that you can interact with using curl or a web browser.
# Get all todos
$ curl http://localhost:5000/todos
[{"id":1,"text":"Learn F#","completed":false}]
# Add a new todo
$ curl -X POST -H "Content-Type: application/json" -d '{"text":"Build a web API"}' http://localhost:5000/todos
# Get all todos again
$ curl http://localhost:5000/todos
[{"id":1,"text":"Learn F#","completed":false},{"id":2,"text":"Build a web API","completed":false}]
Implementation Hints:
- Use
dotnet new web -lang F#to start. Add theGiraffeNuGet package. - Your To-Do items will be stored in an in-memory list for simplicity, managed by a
MailboxProcessorto make it thread-safe. - Your web app’s logic will be a
chooseblock that routes requests:let webApp = choose [ GET >=> route "/todos" >=> handleGetTodos POST >=> route "/todos" >=> handleAddTodo // ... other routes setStatusCode 404 >=> text "Not Found" ] - Each handler (e.g.,
handleGetTodos) is just a function:let handleGetTodos : HttpHandler = fun next ctx -> .... It will fetch the data from your agent, serialize it to JSON, and write it to the response.
Learning milestones:
- You can return a simple “Hello, World” from a GET request → You understand the basic Giraffe setup.
- You can handle multiple routes and HTTP verbs → You understand routing and composition.
- You can read a JSON body and update your application state → You understand model binding and state management.
- You see web development as a function that transforms a request into a response → You have grasped the functional approach to web APIs.
Summary
| Project | Main Language | Difficulty | Key F# Concept Taught |
|—|—|—|—|
| Data Log Processor | F# | Beginner | Functional Pipelines (|>), List processing |
| Board Game Logic | F# | Intermediate | Discriminated Unions, Pattern Matching |
| Concurrent Web Scraper | F# | Advanced | async Workflows, MailboxProcessor |
| Web API with Giraffe | F# | Intermediate | Functional Composition, HTTP Handlers |
(This list can be extended with projects like a “Ray Tracer” (immutability and recursion), a “Type-Safe API Wrapper” (Option type and interoperability), or an “Event Sourcing System” (functional data modeling with fold).)