visit
My motivation was to learn a functional programming language that would make coding for scientific computing and data analysis more expressive, concise, and maintainable, all while fitting seamlessly into the .NET ecosystem that I already know and love. F# fits that bill perfectly.
In contrast, functional programs consist of values and functions, and the application works by applying these functions to values in order to produce new values.This might sound similar to methods in an object oriented style, but there are two key differences:
1. Values cannot be modified (they are said to be immutable). Instead, operations create and return new values.
2. Functions should be pure, meaning that a given input always produces the same output. This is in contrast to a class method, in which some internal state of the class might have an effect on the output of the method.
Let's have a look at this in a code example, in which we want to raise an employee's salary by a given factor:// C# Object Oriented Example
public class Employee
{
public string Name { get; set; }
public float Salary { get; set; }
}
public class SalaryRaiser
{
public float Factor { get; set; }
public void RaiseSalary(Employee employee)
{
// function not pure - depends on Factor, which isn't a parameter
var raise = employee.Salary * Factor;
// employee object is mutated directly
employee.Salary += raise;
}
}
var employee = new Employee { Name = "Bob", Salary = 12345.67F };
var raiser = new SalaryRaiser { Factor = 0.05F };
raiser.RaiseSalary(employee);
// F# Functional Example
type Employee = { Name: string; Salary: float }
let factor = 0.05
let calculateSalary employee factor =
// function is pure - factor is now a parameter
let raise = employee.Salary * factor
// new employee record returned
{ employee with Salary = employee.Salary + raise }
let employee = { Name = "Bob"; Salary = 12345.67 }
let updatedEmployee = calculateSalary employee factor
// C#
public int AddInt(int a, int b)
{
return a + b;
}
var value = AddInt(1, 2)
// F#
let addInt x y = x + y
let value = addInt 1 2
// C#
public float AddFloat(float a, float b)
{
return a + b;
}
int a = 1;
int b = 2;
var value = AddFloat(a, b); // ints are implicitly converted to floats
// F#
let addFloat a:float b:float = a + b // for the purpose of the example, explictly state the a and b must be floats
let value = addFloat 1 2 // will throw exception since 1 and 2 are ints
// instead
let a = float 1
let b = float 2
let value = addFloat a b
Lists
Lists in F# are similar to those in C#, except that (because they are immutable) methods like Add/Remove do not exist. Instead, to perform the equivalent operations, new lists must be created from the existing list.// 1. Declare a list
let list = [1;2;3] // creates a list with the elements 1, 2 and 3
// 2. Declare a list using range syntax
let list = [1..5] // create a list with the element 1, 2, 3, 4, and 5
// 3. Add an element to the start of a list
let list = [1;2;3]
let list2 = 4::list // create a list [4;1;2;3]
// 4. Remove elements from the list
let list = [1;2;3]
let list2 = list.[0..1] // returns a list containing the elements between index 0 and 1. Equivalent to removing the index 3
Sequences
Sequences are similar to lists, except they are lazily evaluated, meaning that the element values are not loaded in to memory until needed. This is useful for dealing with very large sequences. An equivalent very long list has to have all elements stored in memory simulataneously and may even crash your PC!// large list
// WARNING: may crash your pc
let list = [1..1000000] // create a list containing 1 million elements
// large sequence
let sequence = seq {1..1000000}
Tuples
The collections we have looked at previously must contain data all with the same data type (i.e. a list of ints). A tuple is different in that it contain contain multiple values each with different data types.// 1. declare a tuple
let myTuple = (1, "hello")
// 2. destructure a tuple
let myTuple = (1, "hello")
let (num, str) = myTuple // assigns num the value 1, and str the value "hello"
Records
Records group data into distinct objects, similar to classes. Records can even have method. However, as with most things in F#, records are immutable, so 'editing' a record requires you to create a new record from the existing one.// 1. Declare a record type with a method
type Person = {
Name: string;
Age: int;
} with member this.IsAdult = this.Age >= 18
// 2. Create a record
// Note that we don't need to tell the compiler that this is a Person record
// It can infer it from the fields we provide
let sam = { Name = "Sam"; Age = 27 }
// 3. Create a record based on an existing one
let olderSam = { sam with Age = 28 }
Discriminated Unions
Discriminated unions are a powerful feature in F# and may not be familiar to a lot of programmers - they certainly weren't to me! Discriminated unions allow values to be one of a number of named cases, each with potentially different types and values. This is probably shown that explained:// 1. Basic discriminated union
// this declares a Status type, that can have the value Active or Inactive
type Status = Active | Inactive
let myStatus = Status.Active
// 2. A more complex example
type Shape =
| Rectangle of width:float * length:float // takes a tuple of two floats
| Circle of radius:float // takes a float
// both have the Shape type
let cir = Circle 5.
let rect = Rectangle(5., 6.)
Pattern Matching
Pattern matching is very common in F# and is a way of controlling the flow of the application based on the match input value. It can be likened to using if..else statements in this manner, and if statements do exist in F#, but pattern matching is far more idiomatic (and hopefully you'll agree, more powerful).// 1. Pattern matching a discriminated union
type Status = Active | Inactive
// return true if Active, return false if inactive
let isActive status =
match status with
| Active -> true
| Inactive -> false
// 2. Pattern matching to handle errors
type Result =
| Error of string
| Data of int
// handle printing data or error cases to the console
let handle result =
match result with
| Data data -> printfn "My result: %i" data
| Error error -> printfn "An error ocurred: %s" error
//3. Pattern matching tuple values
let matchPoint point =
match point with
| (0, 0) -> printfn "This is the origin"
| (x, 0) -> printfn "X is %i, while Y is 0" x
| (0, y) -> printfn "Y is %i, while X is 0" y
| _ -> printfn "Both values are non-zero" // _ is a catch all case
Currying and Partial Application
A pure function can only have one input and one output, and this is true for functions in F# as well. However, we've already seen some functions with apparently multiple parameters. For example:let addInt x y = x + y
addInt 5 6 // evaluates to 11
let addInt x =
let subFunction y = x + y
subFunction
let intermediate = addInt 5 // evaluates to a function that adds five to its parameter
intermediate 6 // evaluates to 11
let addInt x y = x + y
let add5 = addInt 5 // partially apply addInt to create a new function that adds 5 to the input
add5 6 // evaluates to 11
add5 7 //evaluates to 12
Piping
Piping is a special type of operator that allows you to put the parameter to the function before the function. For example:let addInt x y = x + y
let add5 = 5 |> addInt // pipe 5 into the addInt function
[1; 2; 3] // create a list
|> List.map (fun x -> x + 1) // add 1 to each element of the list
|> List.filter (fun x -> x > 2) // only return elements that have a value greater than 2 (returns [3; 4])
Composition
Composition is the act of joining multiple functions together to create a single new function. This can seem pretty much the same as piping at first glance, but it is subtly different: a pipe has to start with a value and is immediately evaluated to give an output; function composition joins multiple functions together and returns that new function (i.e. nothing is evaluated at this stage).// Example comparing piping and composition
let add5 = (+) 5
let double = (*) 2
// piping
5
|> add5
|> double // evaluates to 20
// composition
let add5ThenDouble = add5 >> double // compose into a new function
add5ThenDouble 5 // evaluates to 20
Previously published .