Functional Programming in C#
How to write better C# code
Enrico Buonanno
  • August 2017
  • ISBN 9781617293955
  • 408 pages
  • printed in black & white

Functional programming can make your head explode. This book stitches it back together.

Daniel Marbach, Particular Software

Functional Programming in C# teaches you to apply functional thinking to real-world problems using the C# language. The book, with its many practical examples, is written for proficient C# programmers with no prior FP experience. It will give you an awesome new perspective.

About the Technology

Functional programming changes the way you think about code. For C# developers, FP techniques can greatly improve state management, concurrency, event handling, and long-term code maintenance. And C# offers the flexibility that allows you to benefit fully from the application of functional techniques. This book gives you the awesome power of a new perspective.

About the book

Functional Programming in C# teaches you to apply functional thinking to real-world problems using the C# language. You'll start by learning the principles of functional programming and the language features that allow you to program functionally. As you explore the many practical examples, you'll learn the power of function composition, data flow programming, immutable data structures, and monadic composition with LINQ.

Table of Contents detailed table of contents

1. Introducing functional programming

1.1. What is this thing called functional programming?

1.1.1. Functions as first-class values

1.1.2. Avoiding state mutation

1.1.3. Writing programs with strong guarantees

1.2. How functional a language is C#?

1.2.1. The functional nature of LINQ

1.2.2. Functional features in C# 6 and C# 7

1.2.3. A more functional future for C#?

1.3. Thinking in functions

1.3.1. Functions as maps

1.3.2. Representing functions in C#

1.4. Higher-order functions

1.4.1. Functions that depend on other functions

1.4.2. Adapter functions

1.4.3. Functions that create other functions

1.5. In practice: avoiding duplication with HOFs

1.5.1. Encapsulating setup and teardown into a HOF

1.5.2. Turning the using statement into a HOF

1.5.3. Code review: tradeoffs of HOFs

1.6. Benefits of functional programming

1.7. Exercises

1.8. Summary

2. Why function purity matters

2.1. What is function purity?

2.1.1. Purity and side effects

2.1.2. Strategies for managing side effects

2.2. Purity and concurrency

2.2.1. Pure functions parallelize well

2.2.2. Parallelizing impure functions

2.2.3. Avoiding state mutation

2.3. Purity and Testability

2.3.1. In practice: a validation scenario

2.3.2. Bringing impure functions under test

2.3.3. Why testing impure functions is hard

2.3.4. Parameterized unit tests

2.3.5. Avoiding header interfaces

2.4. Purity in the context of changes in computing

2.5. Exercises

2.6. Summary

3. Designing function signatures and types

3.1. Function signature design

3.1.1. Arrow notation

3.1.2. How informative is a signature?

3.2. Capturing data with data objects

3.2.1. Primitive types are often not specific enough

3.2.2. Constraining inputs with custom types

3.2.3. Writing "honest" functions

3.2.4. Composing values with tuples and objects

3.3. Modelling the absence of data with Unit

3.3.1. Why using void is not ideal

3.3.2. Bridging the gap between Action and Func with Unit

3.4. Modelling the possible absence of data with Option

3.4.1. The bad APIs you use every day

3.4.2. An introduction to the Option type

3.4.3. Implementing Option

3.4.4. Gaining robustness by using Option instead of null

3.4.5. Option as the natural result type of partial functions

3.5. Exercises

3.6. Summary

4. Patterns in Functional Programming

4.1. Applying a function to the inner values of a structure

4.1.1. Mapping a function onto a sequence of values

4.1.2. Mapping a function onto an optional value

4.1.3. Introducing Functors

4.2. Performing side effects with ForEach

4.3. Chaining functions with Bind

4.3.1. Composing Option-returning functions with Bind

4.3.2. Flattening nested lists with Bind

4.3.3. Actually, it's called a monad

4.3.4. The Return function

4.3.5. Relation between functors and monads

4.4. Filtering values with Where

4.5. Combining Option and IEnumerable with Bind

4.6. Coding at different levels of abstraction

4.6.1. Regular vs elevated values

4.6.2. Crossing levels of abstraction

4.6.3. Map vs Bind, revisited

4.6.4. Working at the right level of abstraction

4.7. Exercises

4.8. Summary

5. Designing programs with function composition

5.1. Function composition

5.1.1. Brushing up on function composition

5.1.2. Method chaining

5.1.3. Composition in the elevated world

5.2. Thinking in terms of data flow

5.2.1. Using LINQ's composable API

5.2.2. Writing functions that compose well

5.3. Programming workflows

5.3.1. A simple workflow for validation

5.3.2. Refactoring with data flow in mind

5.3.3. Composition leads to greater flexibility

5.4. An introduction to functional domain modelling

5.5. An end-to-end server-side workflow

5.5.1. Expressions vs statements

5.5.2. Declarative vs imperative

5.5.3. The functional take on layering

5.6. Exercises

5.7. Summary

6. Functional error handling

6.1. A safer way to represent outcomes

6.1.1. Capturing error details with Either

6.1.2. Core functions for working with Either

6.1.3. Comparing Option and Either

6.2. Chaining operations that may fail

6.3. Validation: a perfect use case for Either

6.3.1. Choosing a suitable representation for errors

6.3.2. Defining an Either-based API

6.3.3. Adding validation logic

6.4. Representing outcomes to client applications

6.4.1. Exposing an Option-like interface

6.4.2. Exposing an Either-like interface

6.4.3. Returning a result DTO

6.5. Variations on the Either theme

6.5.1. Changing between different representations of errors

6.5.2. Specialized versions of Either

6.5.3. Refactoring to Validation and Exceptional

6.5.4. Leaving exceptions behind?

6.6. Exercises

6.7. Summary

7. Structuring an application with functions

7.1. Partial application: supplying arguments piecemeal

7.1.1. Manually enabling partial application

7.1.2. Generalizing partial application

7.1.3. Order of arguments matters

7.2. Overcoming the quirks of method resolution

7.3. Curried functions: optimized for partial application

7.4. Creating a partial-application-friendly API

7.4.1. Types as documentation

7.4.2. Particularizing the data access function

7.5. Modularizing and composing an application

7.5.1. Modularity in OOP

7.5.2. Modularity in FP

7.5.3. Comparing the two

7.5.4. Composing the application

7.6. Reducing a list to a single value

7.6.1. LINQ's Aggregate method

7.6.2. Aggregating validation results

7.6.3. Harvesting validation errors

7.7. Exercises

7.8. Summary

8. Working effectively with multi-argument functions

8.1. Function application in the elevated world

8.1.1. Understanding applicatives

8.1.2. Lifting functions

8.1.3. An introduction to property testing

8.2. Functors, Applicatives, Monads

8.3. The monad laws

8.3.1. Right identity

8.3.2. Left identity

8.3.3. Associativity

8.3.4. Using Bind with multi-argument functions

8.4. Improving readability by using LINQ with any monad

8.4.1. Using LINQ with arbitrary functors

8.4.2. Using LINQ with arbitrary monads

8.4.3. Let, Where and other LINQ clauses

8.5. In practice: when to use Bind vs Apply

8.5.1. Validation with smart constructors

8.5.2. Harvesting errors with the applicative flow

8.5.3. Failing fast with the monadic flow

8.6. Exercises

8.7. Summary

9. Thinking about data functionally

9.1. The pitfalls of state mutation

9.2. Understanding state, identity, and change

9.2.1. Some things never change

9.2.2. Representing change without mutation

9.2.3. Enforcing immutability

9.3. A short introduction to functional data structures

9.3.1. The classic functional linked list

9.3.2. Binary trees

9.4. Exercises

9.5. Summary

10. Event sourcing: a functional approach to persistence

10.1. Thinking functionally about data storage

10.1.1. Why data storage should be append-only

10.1.2. Relax, and forget about storing state

10.2. Event sourcing basics

10.2.1. Representing events

10.2.2. Persisting events

10.2.3. Representing state

10.2.4. An interlude on pattern matching

10.2.5. Representing state transitions

10.2.6. Reconstructing current state from past events

10.3. Architecture of an event sourced system

10.3.1. Handling commands

10.3.2. Handling events

10.3.3. Adding validation

10.3.4. Creating views of the data from events

10.4. Comparing different approaches to immutable storage

10.4.1. Datomic vs Event Store

10.4.2. How event-driven is your domain?

10.5. Summary

11. Lazy computations, continuations, and the beauty of monadic composition

11.1. The virtue of lazyness

11.1.1. Lazy APIs for working with Option

11.1.2. Composing lazy computations

11.2. Exception handling with Try

11.2.1. Representing computations that may fail

11.2.2. Safely extracting information from a JSON object

11.2.3. Composing computations that may fail

11.2.4. Monadic composition: what does it mean?

11.3. Creating a middleware pipeline for DB access

11.3.1. Composing functions that perform setup/teardown

11.3.2. A recipe against the pyramid of doom

11.3.3. Capturing the essence of a middleware function

11.3.4. Implementing the query pattern for Middleware

11.3.5. Adding middleware that times the operation

11.3.6. Adding middleware that manages a DB transaction

11.4. Summary

12. Stateful programs and stateful computations

12.1. Programs that manage state

12.1.1. Maintaining a cache of retrieved resources

12.1.2. Refactoring for testability and error handling

12.1.3. Stateful computations

12.2. A language for generating random data

12.3. A general pattern for stateful computations

12.4. Summary

13. Working with asynchronous computations

13.1. Asynchronous computations

13.1.1. The need for asynchrony

13.1.2. Representing asynchronous operations with Task

13.1.3. Task as a container for a future value

13.1.4. Handling failure

13.1.5. An HTTP API for currency conversion

13.1.6. If it fails, try a few more times

13.1.7. Running asynchronous operations in parallel

13.2. Traversables: working with lists of elevated values

13.2.1. Validating a list of values with monadic Traverse

13.2.2. Harvesting validation errors with applicative Traverse

13.2.3. Applying multiple validators to a single value

13.2.4. Using Traverse with Task to await multiple results

13.2.5. Defining Traverse for single-value structures

13.3. Combining asynchrony and validation (or any other two monadic effects)

13.3.1. The problem of stacked monads

13.3.2. Reducing the number of effects

13.3.3. LINQ expressions with a monad stack

13.4. Summary

14. Data streams and the Reactive Extensions

14.1. Representing data streams with IObservable

14.1.1. A sequence of values in time

14.1.2. Subscribing to an IObservable

14.2. Creating IObservables

14.2.1. Creating a timer

14.2.2. Using Subject to tell an IObservable when it should signal

14.2.3. Creating IObservables form callback-based subscriptions

14.2.4. Creating IObservables from simpler structures

14.3. Transforming and combining data streams

14.3.1. Stream transformations

14.3.2. Combining and partitioning streams

14.3.3. Error handling with IObservable

14.3.4. Putting it all together

14.4. Implementing logic that spans multiple events

14.4.1. Detecting sequences of pressed keys

14.4.2. Reacting to multiple event sources

14.4.3. Notifying when an account becomes overdraft

14.5. When should you use IObservable?

14.6. Summary

15. An introduction to message-passing concurrency

15.1. The need for shared mutable state

15.2. Understanding message-passing concurrency

15.2.1. Implementing agents in C#

15.2.2. Getting started with agents

15.2.3. Using agents to handle concurrent requests

15.2.4. Agents vs. Actors

15.3. Functional APIs, agent-based implementations

15.3.1. Agents as implementation details

15.3.2. Hiding agents behind a conventional API

15.4. Message-passing concurrency in LOB applications

15.4.1. Using an agent to synchronize access to account data

15.4.2. Keeping a registry of accounts

15.4.3. An agent is not an object

15.4.4. Putting it all together

15.5. Summary

What's inside

  • Write readable, team-friendly code
  • Master async and data streams
  • Radically improve error handling
  • Event sourcing and other FP patterns

About the reader

Written for proficient C# programmers with no prior FP experience.

About the author

Enrico Buonanno studied computer science at Columbia University and has 15 years of experience as a developer, architect, and trainer.

placing your order...

Don't refresh or navigate away from the page.
print book $37.49 $49.99 pBook + eBook + liveBook
Additional shipping charges may apply
Prints and ships within 3-5 days
Functional Programming in C# (print book) added to cart
continue shopping
go to cart

eBook $29.99 $39.99 3 formats + liveBook
Functional Programming in C# (eBook) added to cart
continue shopping
go to cart

Prices displayed in rupees will be charged in USD when you check out.
customers also reading

This book 1-hop 2-hops 3-hops

FREE domestic shipping on three or more pBooks