F# for Fun and Profit
  • Introduction
  • Getting started
    • Contents of the book
    • "Why use F#?" in one page
    • Installing and using F#
    • F# syntax in 60 seconds
    • Learning F#
    • Troubleshooting F#
    • Low-risk ways to use F# at work
      • Twenty six low-risk ways to use F# at work
      • Using F# for development and devops scripts
      • Using F# for testing
      • Using F# for database related tasks
      • Other interesting ways of using F# at work
  • Why use F#?
    • The "Why use F#?" Series
      • Introduction to the 'Why use F#' series
      • F# syntax in 60 seconds
      • Comparing F# with C#: A simple sum
      • Comparing F# with C#: Sorting
      • Comparing F# with C#: Downloading a web page
      • Four Key Concepts
      • Conciseness
      • Type inference
      • Low overhead type definitions
      • Using functions to extract boilerplate code
      • Using functions as building blocks
      • Pattern matching for conciseness
      • Convenience
      • Out-of-the-box behavior for types
      • Functions as interfaces
      • Partial Application
      • Active patterns
      • Correctness
      • Immutability
      • Exhaustive pattern matching
      • Using the type system to ensure correct code
      • Worked example: Designing for correctness
      • Concurrency
      • Asynchronous programming
      • Messages and Agents
      • Functional Reactive Programming
      • Completeness
      • Seamless interoperation with .NET libraries
      • Anything C# can do...
      • Why use F#: Conclusion
  • Thinking Functionally
    • The "Thinking Functionally" Series
      • Thinking Functionally: Introduction
      • Mathematical functions
      • Function Values and Simple Values
      • How types work with functions
      • Currying
      • Partial application
      • Function associativity and composition
      • Defining functions
      • Function signatures
      • Organizing functions
      • Attaching functions to types
      • Worked example: A stack based calculator
  • Understanding F# ###
    • The "Expressions and syntax" Series
      • Expressions and syntax: Introduction
      • Expressions vs. statements
      • Overview of F# expressions
      • Binding with let, use, and do
      • F# syntax: indentation and verbosity
      • Parameter and value naming conventions
      • Control flow expressions
      • Exceptions
      • Match expressions
      • Formatted text using printf
      • Worked example: Parsing command line arguments
      • Worked example: Roman numerals
    • The "Understanding F# types" Series
      • Understanding F# types: Introduction
      • Overview of types in F#
      • Type abbreviations
      • Tuples
      • Records
      • Discriminated Unions
      • The Option type
      • Enum types
      • Built-in .NET types
      • Units of measure
      • Understanding type inference
    • Choosing between collection functions
    • The "Object-oriented programming in F#" Series
      • Object-oriented programming in F#: Introduction
      • Classes
      • Inheritance and abstract classes
      • Interfaces
      • Object expressions
    • The "Computation Expressions" Series
      • Computation expressions: Introduction
      • Understanding continuations
      • Introducing 'bind'
      • Computation expressions and wrapper types
      • More on wrapper types
      • Implementing a builder: Zero and Yield
      • Implementing a builder: Combine
      • Implementing a builder: Delay and Run
      • Implementing a builder: Overloading
      • Implementing a builder: Adding laziness
      • Implementing a builder: The rest of the standard methods
    • Organizing modules in a project
    • The "Dependency cycles" Series
      • Cyclic dependencies are evil
      • Refactoring to remove cyclic dependencies
      • Cycles and modularity in the wild
    • The "Porting from C#" Series
      • Porting from C# to F#: Introduction
      • Getting started with direct porting
  • Functional Design ###
    • The "Designing with types" Series
      • Designing with types: Introduction
      • Single case union types
      • Making illegal states unrepresentable
      • Discovering new concepts
      • Making state explicit
      • Constrained strings
      • Non-string types
      • Designing with types: Conclusion
    • Algebraic type sizes and domain modelling
    • Thirteen ways of looking at a turtle
      • Thirteen ways of looking at a turtle (part 2)
      • Thirteen ways of looking at a turtle - addendum
  • Functional Patterns ###
    • How to design and code a complete program
    • A functional approach to error handling (Railway oriented programming)
      • Railway oriented programming: Carbonated edition
    • The "Understanding monoids" Series
      • Monoids without tears
      • Monoids in practice
      • Working with non-monoids
    • The "Understanding Parser Combinators" Series
      • Understanding Parser Combinators
      • Building a useful set of parser combinators
      • Improving the parser library
      • Writing a JSON parser from scratch
    • The "Handling State" Series
      • Dr Frankenfunctor and the Monadster
      • Completing the body of the Monadster
      • Refactoring the Monadster
    • The "Map and Bind and Apply, Oh my!" Series
      • Understanding map and apply
      • Understanding bind
      • Using the core functions in practice
      • Understanding traverse and sequence
      • Using map, apply, bind and sequence in practice
      • Reinventing the Reader monad
      • Map and Bind and Apply, a summary
    • The "Recursive types and folds" Series
      • Introduction to recursive types
      • Catamorphism examples
      • Introducing Folds
      • Understanding Folds
      • Generic recursive types
      • Trees in the real world
    • The "A functional approach to authorization" Series
      • A functional approach to authorization
      • Constraining capabilities based on identity and role
      • Using types as access tokens
  • Testing
    • An introduction to property-based testing
    • Choosing properties for property-based testing
  • Examples and Walkthroughs
    • Worked example: Designing for correctness
    • Worked example: A stack based calculator
    • Worked example: Parsing command line arguments
    • Worked example: Roman numerals
    • Commentary on 'Roman Numerals Kata with Commentary'
    • Calculator Walkthrough: Part 1
      • Calculator Walkthrough: Part 2
      • Calculator Walkthrough: Part 3
      • Calculator Walkthrough: Part 4
    • Enterprise Tic-Tac-Toe
      • Enterprise Tic-Tac-Toe, part 2
    • Writing a JSON parser from scratch
  • Other
    • Ten reasons not to use a statically typed functional programming language
    • Why I won't be writing a monad tutorial
    • Is your programming language unreasonable?
    • We don't need no stinking UML diagrams
    • Introvert and extrovert programming languages
    • Swapping type-safety for high performance using compiler directives
Powered by GitBook
On this page
  • Function-oriented rather than object-oriented
  • Expressions rather than statements
  • Algebraic Types
  • Pattern matching for flow of control
  • Pattern matching with union types ###

Was this helpful?

  1. Why use F#?
  2. The "Why use F#?" Series

Four Key Concepts

The concepts that differentiate F# from a standard imperative language

PreviousComparing F# with C#: Downloading a web pageNextConciseness

Last updated 5 years ago

Was this helpful?

In the next few posts we'll move on to demonstrating the themes of this series: conciseness, convenience, correctness, concurrency and completeness.

But before that, let's look at some of the key concepts in F# that we will meet over and over again. F# is different in many ways from a standard imperative language like C#, but there are a few major differences that are particularly important to understand:

  • Function-oriented rather than object-oriented

  • Expressions rather than statements

  • Algebraic types for creating domain models

  • Pattern matching for flow of control

In later posts, these will be dealt with in much greater depth -- this is just a taster to help you understand the rest of this series.

Function-oriented rather than object-oriented

As you might expect from the term "functional programming", functions are everywhere in F#.

Of course, functions are first class entities, and can be passed around like any other value:

let square x = x * x

// functions as values
let squareclone = square
let result = [1..10] |> List.map squareclone

// functions taking other functions as parameters
let execFunction aFunc aParam = aFunc aParam
let result2 = execFunction square 12

But C# has first-class functions too, so what's so special about functional programming?

The short answer is that the function-oriented nature of F# infiltrates every part of the language and type system in a way that it does not in C#, so that things that are awkward or clumsy in C# are very elegant in F#.

It's hard to explain this in a few paragraphs, but here are some of the benefits that we will see demonstrated over this series of posts:

  • Building with composition. Composition is the 'glue' that allows us build larger systems from smaller ones. This is not an optional technique, but is at the very heart of the functional style. Almost every line of code is a composable expression (see below). Composition is used to build basic functions, and then functions that use those functions, and so on. And the composition principle doesn't just apply to functions, but also to types (the product and sum types discussed below).

  • Factoring and refactoring. The ability to factor a problem into parts depends how easily the parts can be glued back together. Methods and classes that might seem to be indivisible in an imperative language can often be broken down into surprisingly small pieces in a functional design. These fine-grained components typically consist of (a) a few very general functions that take other functions as parameters, and (b) other helper functions that specialize the general case for a particular data structure or application.

Expressions rather than statements

In functional languages, there are no statements, only expressions. That is, every chunk of code always returns a value, and larger chunks are created by combining smaller chunks using composition rather than a serialized list of statements.

If you have used LINQ or SQL you will already be familiar with expression-based languages. For example, in pure SQL, you cannot have assignments. Instead, you must have subqueries within larger queries.

SELECT EmployeeName 
FROM Employees
WHERE EmployeeID IN 
    (SELECT DISTINCT ManagerID FROM Employees)  -- subquery

F# works in the same way -- every function definition is a single expression, not a set of statements.

And it might not be obvious, but code built from expressions is both safer and more compact than using statements. To see this, let's compare some statement-based code in C# with the equivalent expression-based code.

First, the statement-based code. Statements don't return values, so you have to use temporary variables that are assigned to from within statement bodies.

// statement-based code in C#
int result;     
if (aBool)
{
  result = 42; 
}
Console.WriteLine("result={0}", result);

Because the if-then block is a statement, the result variable must be defined outside the statement but assigned to from inside the statement, which leads to issues such as:

  • What initial value should result be set to?

  • What if I forget to assign to the result variable?

  • What is the value of the result variable in the "else" case?

For comparison, here is the same code, rewritten in an expression-oriented style:

// expression-based code in C#
int result = (aBool) ? 42 : 0;
Console.WriteLine("result={0}", result);

In the expression-oriented version, none of these issues apply:

  • The result variable is declared at the same time that it is assigned. No variables have to be set up "outside" the expression and there is no worry about what initial value they should be set to.

  • The "else" is explicitly handled. There is no chance of forgetting to do an assignment in one of the branches.

  • It is not possible to forget to assign result, because then the variable would not even exist!

Expression-oriented style is not a choice in F#, and it is one of the things that requires a change of approach when coming from an imperative background.

Algebraic Types

The type system in F# is based on the concept of algebraic types. That is, new compound types are built by combining existing types in two different ways:

  • First, a combination of values, each picked from a set of types. These are called "product" types.

  • Of, alternately, as a disjoint union representing a choice between a set of types. These are called "sum" types.

For example, given existing types int and bool, we can create a new product type that must have one of each:

//declare it
type IntAndBool = {intPart: int; boolPart: bool}

//use it
let x = {intPart=1; boolPart=false}

Alternatively, we can create a new union/sum type that has a choice between each type:

//declare it
type IntOrBool = 
    | IntChoice of int
    | BoolChoice of bool

//use it
let y = IntChoice 42
let z = BoolChoice true

These "choice" types are not available in C#, but are incredibly useful for modeling many real-world cases, such as states in a state machine (which is a surprisingly common theme in many domains).

Pattern matching for flow of control

Most imperative languages offer a variety of control flow statements for branching and looping:

  • if-then-else (and the ternary version bool ? if-true : if-false)

  • case or switch statements

  • for and foreach loops, with break and continue

  • while and until loops

  • and even the dreaded goto

F# does support some of these, but F# also supports the most general form of conditional expression, which is pattern-matching.

A typical matching expression that replaces if-then-else looks like this:

match booleanExpression with
| true -> // true branch
| false -> // false branch

And the replacement of switch might look like this:

match aDigit with
| 1 -> // Case when digit=1
| 2 -> // Case when digit=2
| _ -> // Case otherwise

Finally, loops are generally done using recursion, and typically look something like this:

match aList with
| [] -> 
     // Empty case 
| first::rest -> 
     // Case with at least one element.
     // Process first element, and then call 
     // recursively with the rest of the list

Although the match expression seems unnecessarily complicated at first, you'll see that in practice it is both elegant and powerful.

Pattern matching with union types ###

We mentioned above that F# supports a "union" or "choice" type. This is used instead of inheritance to work with different variants of an underlying type. Pattern matching works seamlessly with these types to create a flow of control for each choice.

In the following example, we create a Shape type representing four different shapes and then define a draw function with different behavior for each kind of shape. This is similar to polymorphism in an object oriented language, but based on functions.

type Shape =        // define a "union" of alternative structures
| Circle of int 
| Rectangle of int * int
| Polygon of (int * int) list
| Point of (int * int) 

let draw shape =    // define a function "draw" with a shape param
  match shape with
  | Circle radius -> 
      printfn "The circle has a radius of %d" radius
  | Rectangle (height,width) -> 
      printfn "The rectangle is %d high by %d wide" height width
  | Polygon points -> 
      printfn "The polygon is made of these points %A" points
  | _ -> printfn "I don't recognize this shape"

let circle = Circle(10)
let rect = Rectangle(4,5)
let polygon = Polygon( [(1,1); (2,2); (3,3)])
let point = Point(2,3)

[circle; rect; polygon; point] |> List.iter draw

A few things to note:

  • As usual, we didn't have to specify any types. The compiler correctly determined that the shape parameter for the "draw" function was of type Shape.

  • You can see that the match..with logic not only matches against the internal structure of the shape, but also assigns values based on what is appropriate for the shape.

  • The underscore is similar to the "default" branch in a switch statement, except that in F# it is required -- every possible case must always be handled. If you comment out the line

    | _ -> printfn "I don't recognize this shape"

see what happens when you compile!

These kinds of choice types can be simulated somewhat in C# by using subclasses or interfaces, but there is no built in support in the C# type system for this kind of exhaustive matching with error checking.

Once factored out, the generalized functions allow many additional operations to be programmed very easily without having to write new code. You can see a good example of a general function like this (the fold function) in the .

Good design. Many of the principles of good design, such as "separation of concerns", "single responsibility principle", , arise naturally as a result of a functional approach. And functional code tends to be high level and declarative in general.

The following posts in this series will have examples of how functions can make code more concise and convenient, and then for a deeper understanding, there is a whole series on .

And by combining "product" and "sum" types in this way, it is easy to create a rich set of types that accurately models any business domain. For examples of this in action, see the posts on and .

For the benefits of pattern matching, see the post on , and for a worked example that uses pattern matching heavily, see the .

post on extracting duplicate code from loops
"program to an interface, not an implementation"
thinking functionally
low overhead type definitions
using the type system to ensure correct code
exhaustive pattern matching
roman numerals example
four key concepts