Functional Programming in C#, Second Edition

Book description

Real world examples and practical techniques for functional programming in C# without the jargon and theory.

In Functional Programming in C#, Second Edition you will learn how to:

  • Use higher-order functions to reduce duplication and do more with less code
  • Use pure functions to write code that is easy to test and optimize
  • Write pleasant APIs that accurately describe your program's behavior
  • Use dedicated types to handle nullability, system errors, and validation rules predictably and elegantly
  • Write composable code without the overhead of an IoC container

Functional Programming in C# has helped thousands of developers apply functional thinking to C# code. Its practical examples and spot-on treatment of FP concepts makes it the perfect guide for proficient C# programmers. This second edition is fully revised to cover new functional-inspired features in the most recent releases of C#, including tuples, async streams, pattern matching, and records. Each chapter is packed with awesome perspectives and epiphany moments on how functional programming can change the way you code.

About the Technology
Turbocharge your C# code. Good functional techniques will improve concurrency, state management, event handling, and maintainability of your software. This book gives you practical answers to why, how, and where to add functional programing into your C# coding practice.

About the Book
Functional Programming in C#, Second Edition teaches functional thinking for real-world problems. It reviews the C# language features that allow you to program functionally and through many practical examples shows the power of function composition, data-driven programming, and immutable data structures. All code examples work with .NET 6 and C# 10.

What's Inside
  • Higher-order functions reduce duplication and do more with less code
  • Code based on pure functions is easy to test and optimize
  • Write pleasant APIs that accurately describe your program’s behavior
  • Write a Web API in a functional style
  • Monadic composition with LINQ


About the Reader
For intermediate C# programmers.

About the Author
Enrico Buonanno studied Computer Science at Columbia University and has over 15 years of experience as a developer, architect, and trainer.

Quotes
An excellent practical book for using functional programming concepts in your C# applications.
- Foster Haines, J2 Interactive

If this is your first venture into functional programming, then this is the book you want. A great mixture of the new with the familiar.
- Mark Elston, Advantest

A must-read for all .NET developers.
- Jedidja Bourgeois, Freshly Coded Software

An extremely good presentation of functional concepts for C# developers.
- David Paccoud, Bioclinica

Publisher resources

View/Submit Errata

Table of contents

  1. inside front cover
  2. Praise for the first edition
  3. Functional Programming in C#
  4. Copyright
  5. contents
  6. front matter
    1. preface
    2. acknowledgments
    3. about this book
      1. Who should read this book?
      2. How this book is organized: A road map
      3. Coding for real-world applications
      4. Leveraging functional libraries
      5. About the code
      6. liveBook discussion forum
    4. about the author
  7. Part 1. Getting started
  8. 1 Introducing functional programming
    1. 1.1 What is this thing called functional programming?
      1. 1.1.1 Functions as first-class values
      2. 1.1.2 Avoiding state mutation
      3. 1.1.3 Writing programs with strong guarantees
    2. 1.2 How functional a language is C#?
      1. 1.2.1 The functional nature of LINQ
      2. 1.2.2 Shorthand syntax for coding functionally
      3. 1.2.3 Language support for tuples
      4. 1.2.4 Pattern matching and record types
    3. 1.3 What you will learn in this book
    4. Summary
  9. 2 Thinking in functions
    1. 2.1 What’s a function, anyway?
      1. 2.1.1 Functions as maps
      2. 2.1.2 Representing functions in C#
    2. 2.2 Higher-order functions (HOFs)
      1. 2.2.1 Functions that depend on other functions
      2. 2.2.2 Adapter functions
      3. 2.2.3 Functions that create other functions
    3. 2.3 Using HOFs to avoid duplication
    4. Exercises
    5. Summary
  10. 3 Why function purity matters
    1. 3.1 What is function purity?
      1. 3.1.1 Purity and side effects
      2. 3.1.2 Strategies for managing side effects
      3. 3.1.3 Avoid mutating arguments
    2. 3.2 Enabling parallelization by avoiding state mutation
      1. 3.2.1 Pure functions parallelize well
      2. 3.2.2 Parallelizing impure functions
      3. 3.2.3 Avoiding state mutation
    3. 3.3 Purity and testability
      1. 3.3.1 Isolating I/O effects
      2. 3.3.2 A business validation scenario
      3. 3.3.3 Why testing impure functions is hard
    4. 3.4 Testing code that performs I/O
      1. 3.4.1 Object-oriented dependency injection
      2. 3.4.2 Testability without so much boilerplate
    5. 3.5 Purity and the evolution of computing
    6. Exercises
    7. Summary
  11. Part 2. Core techniques
  12. 4 Designing function signatures and types
    1. 4.1 Designing function signatures
      1. 4.1.1 Writing functions signatures with arrow notation
      2. 4.1.2 How informative is a signature?
    2. 4.2 Capturing data with data objects
      1. 4.2.1 Primitive types are often not specific enough
      2. 4.2.2 Constraining inputs with custom types
      3. 4.2.3 Writing honest functions
      4. 4.2.4 Composing values into complex data objects
    3. 4.3 Modeling the absence of data with Unit
      1. 4.3.1 Why void isn’t ideal
      2. 4.3.2 Bridging the gap between Action and Func
    4. Summary
  13. 5 Modeling the possible absence of data
    1. 5.1 The bad APIs you use every day
    2. 5.2 An introduction to the Option type
    3. 5.3 Implementing Option
      1. 5.3.1 An idealized implementation of Option
      2. 5.3.2 Consuming an Option
      3. 5.3.3 Creating a None
      4. 5.3.4 Creating a Some
      5. 5.3.5 Optimizing the Option implementation
    4. 5.4 Option as the natural result type of partial functions
      1. 5.4.1 Parsing strings
      2. 5.4.2 Looking up data in a collection
      3. 5.4.3 The smart constructor pattern
    5. 5.5 Dealing with null
      1. 5.5.1 Why null is such a terrible idea
      2. 5.5.2 Gaining robustness by using Option instead of null
      3. 5.5.3 Non-nullable reference types?
      4. 5.5.4 Bulletproof against NullReferenceException
    6. Exercises
    7. Summary
  14. 6 Patterns in functional programming
    1. 6.1 Applying a function to a structure’s inner values
      1. 6.1.1 Mapping a function onto a sequence
      2. 6.1.2 Mapping a function onto an Option
      3. 6.1.3 How Option raises the level of abstraction
      4. 6.1.4 Introducing functors
    2. 6.2 Performing side effects with ForEach
    3. 6.3 Chaining functions with Bind
      1. 6.3.1 Combining Option-returning functions
      2. 6.3.2 Flattening nested lists with Bind
      3. 6.3.3 Actually, it’s called a monad
      4. 6.3.4 The Return function
      5. 6.3.5 Relationship between functors and monads
    4. 6.4 Filtering values with Where
    5. 6.5 Combining Option and IEnumerable with Bind
    6. 6.6 Coding at different levels of abstraction
      1. 6.6.1 Regular vs. elevated values
      2. 6.6.2 Crossing levels of abstraction
      3. 6.6.3 Map vs. Bind, revisited
      4. 6.6.4 Working at the right level of abstraction
    7. Exercises
    8. Summary
  15. 7 Designing programs with function composition
    1. 7.1 Function composition
      1. 7.1.1 Brushing up on function composition
      2. 7.1.2 Method chaining
      3. 7.1.3 Composition in the elevated world
    2. 7.2 Thinking in terms of data flow
      1. 7.2.1 Using LINQ’s composable API
      2. 7.2.2 Writing functions that compose well
    3. 7.3 Programming workflows
      1. 7.3.1 A simple workflow for validation
      2. 7.3.2 Refactoring with data flow in mind
      3. 7.3.3 Composition leads to greater flexibility
    4. 7.4 An introduction to functional domain modeling
    5. 7.5 An end-to-end server-side workflow
      1. 7.5.1 Expressions vs. statements
      2. 7.5.2 Declarative vs. imperative
      3. 7.5.3 The functional take on layering
    6. Exercises
    7. Summary
  16. Part 3. Functional designs
  17. 8 Functional error handling
    1. 8.1 A safer way to represent outcomes
      1. 8.1.1 Capturing error details with Either
      2. 8.1.2 Core functions for working with Either
      3. 8.1.3 Comparing Option and Either
    2. 8.2 Chaining operations that may fail
    3. 8.3 Validation: A perfect use case for Either
      1. 8.3.1 Choosing a suitable representation for errors
      2. 8.3.2 Defining an Either-based API
      3. 8.3.3 Adding validation logic
    4. 8.4 Representing outcomes to client applications
      1. 8.4.1 Exposing an Option-like interface
      2. 8.4.2 Exposing an Either-like interface
      3. 8.4.3 Returning a result DTO
    5. 8.5 Variations on the Either theme
      1. 8.5.1 Changing between different error representations
      2. 8.5.2 Specialized versions of Either
      3. 8.5.3 Refactoring to Validation and Exceptional
      4. 8.5.4 Leaving exceptions behind?
    6. Exercises
    7. Summary
  18. 9 Structuring an application with functions
    1. 9.1 Partial application: Supplying arguments piecemeal
      1. 9.1.1 Manually enabling partial application
      2. 9.1.2 Generalizing partial application
      3. 9.1.3 Order of arguments matters
    2. 9.2 Overcoming the quirks of method resolution
    3. 9.3 Curried functions: Optimized for partial application
    4. 9.4 Creating a partial-application-friendly API
      1. 9.4.1 Types as documentation
      2. 9.4.2 Particularizing the data access function
    5. 9.5 Modularizing and composing an application
      1. 9.5.1 Modularity in OOP
      2. 9.5.2 Modularity in FP
      3. 9.5.3 Mapping functions to API endpoints
      4. 9.5.4 Comparing the two approaches
    6. 9.6 Reducing a list to a single value
      1. 9.6.1 LINQ’s Aggregate method
      2. 9.6.2 Aggregating validation results
      3. 9.6.3 Harvesting validation errors
    7. Exercises
    8. Summary
  19. 10 Working effectively with multi-argument functions
    1. 10.1 Function application in the elevated world
      1. 10.1.1 Understanding applicatives
      2. 10.1.2 Lifting functions
      3. 10.1.3 An introduction to property-based testing
    2. 10.2 Functors, applicatives, and monads
    3. 10.3 The monad laws
      1. 10.3.1 Right identity
      2. 10.3.2 Left identity
      3. 10.3.3 Associativity
      4. 10.3.4 Using Bind with multi-argument functions
    4. 10.4 Improving readability by using LINQ with any monad
      1. 10.4.1 Using LINQ with arbitrary functors
      2. 10.4.2 Using LINQ with arbitrary monads
      3. 10.4.3 The LINQ clauses let, where, and others
    5. 10.5 When to use Bind vs. Apply
      1. 10.5.1 Validation with smart constructors
      2. 10.5.2 Harvesting errors with the applicative flow
      3. 10.5.3 Failing fast with the monadic flow
    6. Exercises
    7. Summary
  20. 11 Representing state and change
    1. 11.1 The pitfalls of state mutation
    2. 11.2 Understanding state, identity, and change
      1. 11.2.1 Some things never change
      2. 11.2.2 Representing change without mutation
    3. 11.3 Using records to capture the state of domain entities
      1. 11.3.1 Fine-grained control on record initialization
      2. 11.3.2 Immutable all the way down
    4. 11.4 Separating data and logic
    5. Summary
  21. 12 A short introduction to functional data structures
    1. 12.1 The classic functional linked list
      1. 12.1.1 Common list operations
      2. 12.1.2 Modifying an immutable list
      3. 12.1.3 Destructuring any IEnumerable
    2. 12.2 Binary trees
      1. 12.2.1 Common tree operations
      2. 12.2.2 Structure sharing
    3. 12.3 In conclusion
    4. Exercises
    5. Summary
  22. 13 Event sourcing: A functional approach to persistence
    1. 13.1 Thinking functionally about data storage
      1. 13.1.1 Why data storage should be append-only
      2. 13.1.2 Relax and forget about storing state
    2. 13.2 Event sourcing basics
      1. 13.2.1 Representing events
      2. 13.2.2 Persisting events
      3. 13.2.3 Representing state
      4. 13.2.4 Representing state transitions
      5. 13.2.5 Reconstructing the current state from past events
    3. 13.3 Architecture of an event-sourced system
      1. 13.3.1 Handling commands
      2. 13.3.2 Handling events
      3. 13.3.3 Adding validation
      4. 13.3.4 Creating views of the data from events
    4. 13.4 Comparing different approaches to immutable storage
      1. 13.4.1 Datomic vs. Event Store
      2. 13.4.2 How event-driven is your domain?
    5. Summary
  23. Part 4. Advanced techniques
  24. 14 Lazy computations, continuations, and the beauty of monadic composition
    1. 14.1 The virtue of laziness
      1. 14.1.1 Lazy APIs for working with Option
      2. 14.1.2 Composing lazy computations
    2. 14.2 Exception handling with Try
      1. 14.2.1 Representing computations that may fail
      2. 14.2.2 Safely extracting information from a JSON object
      3. 14.2.3 Composing computations that may fail
      4. 14.2.4 Monadic composition: What does it mean?
    3. 14.3 Creating a middleware pipeline for DB access
      1. 14.3.1 Composing functions that perform setup/teardown
      2. 14.3.2 A recipe against the pyramid of doom
      3. 14.3.3 Capturing the essence of a middleware function
      4. 14.3.4 Implementing the query pattern for middleware
      5. 14.3.5 Adding middleware that times the operation
      6. 14.3.6 Adding middleware that manages a DB transaction
    4. Summary
  25. 15 Stateful programs and stateful computations
    1. 15.1 Programs that manage state
      1. 15.1.1 Caching data in memory
      2. 15.1.2 Refactoring for testability and error handling
      3. 15.1.3 Stateful computations
    2. 15.2 A language for generating random data
      1. 15.2.1 Generating random integers
      2. 15.2.2 Generating other primitives
      3. 15.2.3 Generating complex structures
    3. 15.3 A general pattern for stateful computations
    4. Summary
  26. 16 Working with asynchronous computations
    1. 16.1 Asynchronous computations
      1. 16.1.1 The need for asynchrony
      2. 16.1.2 Representing asynchronous operations with Task
      3. 16.1.3 Task as a container for a future value
      4. 16.1.4 Handling failure
      5. 16.1.5 An HTTP API for currency conversion
      6. 16.1.6 If it fails, try a few more times
      7. 16.1.7 Running asynchronous operations in parallel
    2. 16.2 Async streams
      1. 16.2.1 Reading from a file as an async stream
      2. 16.2.2 Consuming async streams functionally
      3. 16.2.3 Consuming data from several streams
      4. 16.2.4 Aggregation and sorting with async streams
    3. Summary
  27. 17 Traversable and stacked monads
    1. 17.1 Traversables: Working with lists of elevated values
      1. 17.1.1 Validating a list of values with monadic Traverse
      2. 17.1.2 Harvesting validation errors with applicative Traverse
      3. 17.1.3 Applying multiple validators to a single value
      4. 17.1.4 Using Traverse with Task to await multiple results
      5. 17.1.5 Defining Traverse for single-value structures
    2. 17.2 Combining asynchrony and validation (or any other two monadic effects)
      1. 17.2.1 The problem of stacked monads
      2. 17.2.2 Reducing the number of effects
      3. 17.2.3 LINQ expressions with a monad stack
    3. Summary
  28. 18 Data streams and the Reactive Extensions
    1. 18.1 Representing data streams with IObservable
      1. 18.1.1 A sequence of values in time
      2. 18.1.2 Subscribing to an IObservable
    2. 18.2 Creating IObservables
      1. 18.2.1 Creating a timer
      2. 18.2.2 Using Subject to tell an IObservable when it should signal
      3. 18.2.3 Creating IObservables from callback-based subscriptions
      4. 18.2.4 Creating IObservables from simpler structures
    3. 18.3 Transforming and combining data streams
      1. 18.3.1 Stream transformations
      2. 18.3.2 Combining and partitioning streams
      3. 18.3.3 Error handling with IObservable
      4. 18.3.4 Putting it all together
    4. 18.4 Implementing logic that spans multiple events
      1. 18.4.1 Detecting sequences of pressed keys
      2. 18.4.2 Reacting to multiple event sources
      3. 18.4.3 Notifying when an account becomes overdrawn
    5. 18.5 When should you use IObservable?
    6. Summary
  29. 19 An introduction to message-passing concurrency
    1. 19.1 The need for shared mutable state
    2. 19.2 Understanding message-passing concurrency
      1. 19.2.1 Implementing agents in C#
      2. 19.2.2 Getting started with agents
      3. 19.2.3 Using agents to handle concurrent requests
      4. 19.2.4 Agents vs. actors
    3. 19.3 Functional APIs, agent-based implementations
      1. 19.3.1 Agents as implementation details
      2. 19.3.2 Hiding agents behind a conventional API
    4. 19.4 Message-passing concurrency in LOB applications
      1. 19.4.1 Using an agent to synchronize access to account data
      2. 19.4.2 Keeping a registry of accounts
      3. 19.4.3 An agent is not an object
      4. 19.4.4 Putting it all together
    5. Summary
  30. Appendix A. Working with previous version of C#
    1. A.1 Immutable data objects before C# 9
      1. A.1.1 Immutability by convention
      2. A.1.2 Defining copy methods
      3. A.1.3 Enforcing immutability
      4. A.1.4 Immutable all the way down
      5. A.1.5 Copy methods without boilerplate?
      6. A.1.6 Comparing strategies for immutability
    2. A.2 Pattern matching before C# 8
      1. A.2.1 C#'s incremental support for pattern matching
      2. A.2.2 A custom solution for pattern matching expressions
    3. A.3 Revisiting the event sourcing example
    4. A.4 In conclusion
  31. Epilogue. What next?
  32. index

Product information

  • Title: Functional Programming in C#, Second Edition
  • Author(s): Enrico Buonanno
  • Release date: January 2022
  • Publisher(s): Manning Publications
  • ISBN: 9781617299827