namespace System
val x : int

Full name: index.x
val time : DateTime

Full name: index.time
Multiple items
type DateTime =
  struct
    new : ticks:int64 -> DateTime + 10 overloads
    member Add : value:TimeSpan -> DateTime
    member AddDays : value:float -> DateTime
    member AddHours : value:float -> DateTime
    member AddMilliseconds : value:float -> DateTime
    member AddMinutes : value:float -> DateTime
    member AddMonths : months:int -> DateTime
    member AddSeconds : value:float -> DateTime
    member AddTicks : value:int64 -> DateTime
    member AddYears : value:int -> DateTime
    ...
  end

Full name: System.DateTime

--------------------
DateTime()
   (+0 other overloads)
DateTime(ticks: int64) : unit
   (+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : unit
   (+0 other overloads)
property DateTime.Now: DateTime
val x : unit

Full name: index.x
val add : x:int -> y:int -> int

Full name: index.add
val x : int
val y : int
val y : float

Full name: index.y
val z : bool

Full name: index.z
val w : string

Full name: index.w
val v : DateTime

Full name: index.v
type MyString = string

Full name: index.MyString
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
type MyInt = int

Full name: index.MyInt
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
type Author =
  {Name: string;
   Age: int;}

Full name: index.Author
Author.Name: string
Author.Age: int
val tomas : Author

Full name: index.tomas
val tomas2 : Author

Full name: index.tomas2
val tomasInOneYear : Author

Full name: index.tomasInOneYear
val x : string * int

Full name: index.x
val name : string

Full name: index.name
val age : int

Full name: index.age
type Shape =
  | Rectangle of int * int
  | Circle of Radius: int

Full name: index.Shape
union case Shape.Rectangle: int * int -> Shape
union case Shape.Circle: Radius: int -> Shape
val shapes : Shape list

Full name: index.shapes
val myFun : x:int -> y:int -> int

Full name: index.myFun
Multiple items
type MeasureAttribute =
  inherit Attribute
  new : unit -> MeasureAttribute

Full name: Microsoft.FSharp.Core.MeasureAttribute

--------------------
new : unit -> MeasureAttribute
[<Measure>]
type Km

Full name: index.Km
[<Measure>]
type Hour

Full name: index.Hour
val x : float<Km/Hour>

Full name: index.x
val myFun : x:string -> y:string -> string

Full name: index.myFun
val x : string
val y : string
val add5 : y:int -> int

Full name: index.add5
val add5 : (int -> int)

Full name: index.add5
val addWith : adder:('a -> 'b) -> x:'a -> 'b

Full name: index.addWith
val adder : ('a -> 'b)
val x : 'a
val f : ('a -> 'b)
val f1 : 'a
val f2 : 'b
val a : 'c
Multiple items
module Option

from Microsoft.FSharp.Core

--------------------
type Option<'T> =
  | None
  | Some of 'T

Full name: index.Option<_>
union case Option.None: Option<'T>
union case Option.Some: 'T -> Option<'T>
val name : obj

Full name: index.name
val age : obj

Full name: index.age
val maybeAdd : x:int -> maybeY:Option<int> -> int

Full name: index.maybeAdd
val maybeY : Option<int>
val isSome : s:Option<'a> -> bool

Full name: index.isSome
val s : Option<'a>
type Person =
  {Name: string;
   Age: int;}

Full name: index.Person
Person.Name: string
Person.Age: int
val tomas : Person

Full name: index.tomas
val intList : int list

Full name: index.intList
val intList : int []

Full name: index.intList
val intSeq1 : seq<int>

Full name: index.intSeq1
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

--------------------
type seq<'T> = Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
val intSeq2 : seq<int>

Full name: index.intSeq2
val sumAll : xs:int list list -> int

Full name: index.sumAll
val xs : int list list
val xs' : int list
val sumAll2 : xs:int list list -> int

Full name: index.sumAll2
val inner : (int list list -> int -> int)
val acc : int
val add5 : x:int -> int

Full name: index.add5
val maybeAdd5 : (int option -> int option)

Full name: index.maybeAdd5
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
val safeDiv10 : y:int -> Option<int>

Full name: index.safeDiv10
val optSafeDiv10 : (int option -> obj option)

Full name: index.optSafeDiv10
val bind : binder:('T -> 'U option) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.bind
val numbers : int list

Full name: index.numbers
val evenNumbers : int list

Full name: index.evenNumbers
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val filter : predicate:('T -> bool) -> list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.filter
val sum : int

Full name: index.sum
val fold : folder:('State -> 'T -> 'State) -> state:'State -> list:'T list -> 'State

Full name: Microsoft.FSharp.Collections.List.fold
Multiple items
val double : int list

Full name: index.double

--------------------
type double = Double

Full name: Microsoft.FSharp.Core.double
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val doubleSum : int

Full name: index.doubleSum
module Conversion

from index
val priceToPoints : price:obj -> obj

Full name: index.Conversion.priceToPoints
val price : obj
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
module Customer

from index.Conversion
module Ticket

from index.Conversion.Customer
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val ofList : elements:('Key * 'T) list -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.ofList
val toList : table:Map<'Key,'T> -> ('Key * 'T) list (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.toList
val add : key:'Key -> value:'T -> table:Map<'Key,'T> -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.add
val find : key:'Key -> table:Map<'Key,'T> -> 'T (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.find
val tryFind : key:'Key -> table:Map<'Key,'T> -> 'T option (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.tryFind
val mutable x : int

Full name: index.Conversion.Customer.Ticket.x
Multiple items
val mutable list : int list

Full name: index.Conversion.Customer.Ticket.list

--------------------
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
Multiple items
type Person =
  new : name:string * age:int -> Person
  member Age : int
  member IsAdult : bool
  member Name : string

Full name: index.Conversion.Customer.Ticket.Person

--------------------
new : name:string * age:int -> Person
val name : string
val age : int
val isAdult : bool
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val this : Person
member Person.Name : string

Full name: index.Conversion.Customer.Ticket.Person.Name
member Person.Age : int

Full name: index.Conversion.Customer.Ticket.Person.Age
member Person.IsAdult : bool

Full name: index.Conversion.Customer.Ticket.Person.IsAdult
val tomas : Person

Full name: index.Conversion.Customer.Ticket.tomas
type Adder =
  interface
    abstract member Add : int -> int -> int
  end

Full name: index.Conversion.Customer.Ticket.Adder
abstract member Adder.Add : int -> int -> int

Full name: index.Conversion.Customer.Ticket.Adder.Add
Multiple items
type AdderImpl =
  interface Adder
  new : unit -> AdderImpl

Full name: index.Conversion.Customer.Ticket.AdderImpl

--------------------
new : unit -> AdderImpl
val this : AdderImpl
override AdderImpl.Add : x:int -> y:int -> int

Full name: index.Conversion.Customer.Ticket.AdderImpl.Add
val adder : AdderImpl

Full name: index.Conversion.Customer.Ticket.adder
val adder : Adder

Full name: index.Conversion.Customer.Ticket.adder
val this : Adder
abstract member Adder.Add : int -> int -> int
val private destinationList : IComparable list

Full name: index.Conversion.Customer.Ticket.destinationList
val mapi : mapping:(int -> 'T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.mapi
val private distanceList : Map<(IComparable * IComparable),obj>

Full name: index.Conversion.Customer.Ticket.distanceList
val collect : mapping:('T -> 'U list) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.collect
val index1 : int
val index2 : int
val distance : float
val typedDistance : obj
val private priceList : obj list

Full name: index.Conversion.Customer.Ticket.priceList
val economyPrice : 'a
val businessPrice : 'a
val firstClassPrice : 'a
val typedEconomyPrice : (((IComparable * IComparable) * 'a) list -> 'b)
val typedBusinessPrice : (((IComparable * IComparable) * 'a) list -> 'b)
val typedFirstClassPrice : (((IComparable * IComparable) * 'a) list -> 'b)
val prices : (((IComparable * IComparable) * 'a) list -> 'b) * (((IComparable * IComparable) * 'a) list -> 'b) * 'b
Multiple items
type DestinationService =
  new : unit -> DestinationService

Full name: index.Conversion.Customer.Ticket.DestinationService

--------------------
new : unit -> DestinationService
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>

FSharpIntro

An introduction to the beautiful world of F#

@TomasJansson

Agenda of the workshop

  • A brief introduction to what F# is
  • Introduction to exercises
  • Module 1 - Modelling with types, and get used to the tooling
  • Module 2 - Functions, operators, pattern matching, option and little bit of lists
  • Module 3 - More options, lists
  • Module 4 - Maps and basic OO
  • Module 5 - Composition

Links:

What is F#?

Open source functional-first programming language

  • Functional-first -> easier to write functional code

Declarative over imperative

  • The code focuses more on what to do, instead of how to do it

Concise

  • Better type system and less syntax allows for less noise

Better defaults

  • Non-null by default - fewer unexpected bugs
  • Immutable - makes it easier to reason

Resources

Most of what you need you'll find in this material, but it is good to know some more resources

Exercises

Goal, structure and exercise description

Goal

  • F# fundamentals
  • Scripts and applications
  • Functional concepts

Structure

  • 5 different modules
  • Each module focuses on different topics (but might borrow from each other)
  • Theory between each set of exercises
  • Exercises that covers most of the theory

Exercise description

  • Flight ticketing application
  • Use cases:
    • Add user
    • List users
    • Reserve ticket
    • Ticket should be automatically upgraded for gold members
    • Point for distance and spending
    • Point might give you gold member status
    • List tickets for customer

Module 1

Modelling with types, and get used to the tooling

The tools

  • REPL (Read Evaluate Print Loop)
    • End line with ;; to mark the end of the input
    • C:\Program Files (x86)\Microsoft SDKs\F#\4.1\Framework\v4.0\fsi.exe (tip: add to path)
  • Visual Studio
    • Alt+Enter - send selection to REPL/Interactive window
  • Visual Studio code
    • Alt+Enter - send selection to REPL/Interactive window

Script files

  • You can reference dll files, other script and source files
  • Use it to explore code quickly without a project
  • Use open instead of using to reference a namespace
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
#r "APathToA.dll"
#load "APathToAnotherScript.fsx"
#load "APathToASourceFile.hs"

open System

let x = 10
let time = DateTime.Now
  • Execute in two ways:
    • Alt+Enter in IDE
    • fsi --exec file.fsx

Applications

Same as for C# but with some minor changes

  • Source code are kept in .fs files
  • The project file is a .fsproj instead of .csproj
  • File ordering matter, files must be in dependency order in the project file
  • F# projects can reference C# dlls
  • C# projects can reference F# dlls
  • You can mix F# and C# in one solution

Expressions

Everything in F# is an expression

1: 
let x = if true then 100
  • Doesn't compile since if is an expression, not a statement like in C#.
  • Return values are used instead of mutation
  • (in the sample above the else clause has an implicit unit return value)

Bindings

When assigning a variable a value, you bind that variable to the value. The value can be a function or an actual value

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
// bind the value 5 to the variable x
let x = 5

// bind the function x + y to add
let add = fun x y -> x + y

// false, here it is a pattern match and not bind
x = x + 1

Types 1

  • Built in types (those that are used in the workshop)
1: 
2: 
3: 
4: 
5: 
let x = 1                   // int
let y = 1.                  // the . indicates that this is a float
let z = true                // bool
let w = "tomas"             // string
let v = System.DateTime.Now // DateTime
  • Type aliases
1: 
2: 
type MyString = string
type MyInt = int

Types 2

  • Record types
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
type Author = {Name: string; Age: int}
let tomas = {Name = "tomas"; Age = 35}
let tomas2 = 
    {
        Name = "tomas"
        Age = 35
    }
tomas = tomas2 // true, because of structural comparison
               // and = is equal and not assignment
// Create new record based on old one
let tomasInOneYear = {tomas with Age = tomas.Age + 1}
tomas = tomasInOneYear // false
  • Tuples
1: 
2: 
let x = ("tomas", 35)
let (name, age) = x

Types 3

  • Discriminated union
1: 
2: 
3: 
4: 
5: 
type Shape = 
    | Rectangle of int * int
    | Circle of Radius:int

let shapes = [ Rectangle(1,69); Circle(69) ]
  • Function types (finally some functions)
1: 
2: 
// myFun : x:int -> y:int -> int (this is the type of the function)
let myFun x y = x + y
  • Measure types
1: 
2: 
3: 
type [<Measure>] Km
type [<Measure>] Hour
let x = 10.<Km>/1.<Hour> // x: float<Km/Hour>

Gotchas

Now when you've seen some code it is time for some gotchas:

  • Order matters
  • Structure matters (F# uses signficant space)
  • F# types use structural equality by default
  • F# types are immutable by default
  • F# types are not nullable by default
  • F# has good type inference (but sometimes you need to help the compiler)
1: 
2: 
3: 
4: 
//         in type1   in type2    return type
//            |          |           |
//            V          V           V
let myFun (x:string) (y:string) : string = x + y

Exercises - Module 1

Goal

  • Learn how to create types
  • Learn how to use the FSI to experiment
  • Try to play around with the code while going through the exercises

Exercise 1.1 - getting started

Exercise 1.2 - create customer types

  • Type alias named CustomerId of type int
  • Measure type called Point
  • Points type of type float<Point>
  • Type alias CustomerName of type string
  • Record type GoldData that has a property PromotedDate of type DateTime

Exercise 1.2 - create customer types (cont.)

  • Discriminated union type FrequentFlyerStatus with two cases
    • Regular with no field
    • Gold with one field of type GoldData
  • Record type Customer that has the following properties
    • Id of type CustomerId
    • Name of type CustomerName
    • Points of type Points
    • FrequentFlyerStatus of type FrequentFlyerStatus

Exercise 1.3 - create destination types

  • Measure type called Km
  • Distance type of type float<Km>
  • Type alias named DestinationName of type string
  • Type alias named DestinationId of type int
  • Record type Destination that has the following properties
    • Id of type DestinationId
    • Name of type DestinationName

Exercise 1.4 - create ticket types

  • Measure type called SEK
  • Price type of type float<SEK>
  • Type alias named DestinationName of type string
  • Type alias named DestinationId of type int
  • Discriminated union type TicketClass with three cases
    • Economy with no field
    • Business with no field
    • FirstClass with no field

Exercise 1.4 - create ticket types (cont.)

  • Record type Ticket that has the following properties
    • CustomerId of type CustomerId
    • Price of type Price
    • TicketClass of type TicketClass
    • From of type Destination
    • To of type Destination

Module 2

Functions, operators, pattern matching, option and little bit of lists

Functions

Functions in F# are curried, that is, they only have 1 argument!

1: 
2: 
let add x y = x + y
let add x = fun y -> x + y

Partial applications

When you apply a function partially you get a new function back

1: 
2: 
3: 
let add x y = x + y
let add5 y = add 5 y
let add5 = add 5        // fun y -> 5 + y

Higher order functions

You can both use functions as argument or return values of functions

  • Returning a function
1: 
2: 
let add x y = x + y
let add5 = add 5
  • Function as argument
1: 
2: 
3: 
4: 
let add x y = x + y
let add5 = add 5
let addWith adder x = adder x
addWith add5 5                  // 10

Operators

Functional operators are common in F#, and they are not as complicated as they seem. Two of the most common ones are the pipeline operator and function composition. You can easily define your own as well.

  • |> pipeline operator
1: 
2: 
3: 
(|>)    // ('a -> ('a -> 'b) -> 'b)

let (|>>>) = fun x f -> f x
  • >> function composition
1: 
2: 
3: 
(>>)     // (('a -> 'b) -> ('b -> 'c) -> 'a -> 'c)

let (>>>>) = fun f1 f2 a = f2 (f1 a)

Options

Built in type to use instead of null --> force explicit handling of missing values and a semantic meaning. It is implemented as a generic discriminated union.

1: 
2: 
3: 
type Option<'T> = 
    | None
    | Some of 'T

Pattern matching

Can be used with any F# types:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
// Pattern match on tuple
let myTuple = "tomas"*35
let (name, age) = myTuple

// Pattern match on discriminated union
let maybeAdd x maybeY = 
    match maybeY with
    | None -> x
    | Some y -> x + y

let isSome s = 
    match s with
    | None -> false
    | Some _ -> true // _ don't care match

Pattern match (cont.)

1: 
2: 
3: 
4: 
// Pattern match on record type
type Person = {Name: string; Age: int}
let tomas = {Name = "tomas"; Age = 35}
let {Name = name; Age = age} = tomas

Lists, arrays and sequences

  • Lists
    • immutable native F# lists
1: 
2: 
let intList = [1;2;3]
let intList = [1..3]
  • Arrays
    • mutable arrays, more memory efficient
1: 
2: 
let intList = [|1;2;3|]
let intList = [|1..3|]
  • Sequences (IEnumerable)
    • lazy
    • can be infinite
1: 
2: 
let intSeq1 = seq { yield 1; yield 2}
let intSeq2 = seq { yield! intSeq1; yield 3; yield 4}

Pattern match on lists

Of course you can pattern match on lists as well.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let rec sumAll xs = 
    match xs with
    | [] -> 0                       // base case
    | [x::xs'] -> x + sumAll xs'    // recursive case

// With tail recursion to avoid stack overflow
let sumAll2 xs = 
    let rec inner xs acc = 
        match xs with
        | [] -> acc
        | [x::xs'] -> inner xs' (acc + x)
    inner xs 0

Member functions

You can also attach functions to types

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
type Person =
    {
        Name: string
        Age: int
    }
    with 
        static member create name age = {Name = name; Age = age}
        member this.isAdult() = this.Age > 18

let tomas = {Name = "tomas"; Age = 35}
tomas.isAdult() // true

Exercises - Module 2

Goal

  • Apply some structure to the project
  • Basic functionality

Exercise 2.1 - getting started

  • If you finished module1 you can continue with the code
  • If you did not finish module1, checkout module2
  • Copy all the types from Script.fsx to Types.fsx

Exercise 2.2 - Customer functions

  • Open the test project
  • Comment out the first test in the CustomerTests module inside TypesTest.fs
  • Implement a static create function on the Customer type
  • Comment out the last two and implement the instance function isGoldMember()

Exercise 2.3 - Destination functions

  • Comment out the first test in the DestinationTests module inside TypesTest.fs
  • Implement a static create function on the Destination type

Exercise 2.4 - Ticket functions

  • Comment out the first test in the TicketTests module inside TypesTest.fs
  • Implement a static create function on the Ticket type

Module 3

More options, lists

Options level 2

Option.map "lifts" a regular function a function that handles option

1: 
2: 
3: 
4: 
let add5 x = 5 + x
let maybeAdd5 = Option.map add5
maybeAdd5 None          // None
maybeAdd5 (Some 10)     // Some 15

Option.bind "lifts" a function that does not take an Option as input but returns it to a function that only deals with Option types

1: 
2: 
3: 
4: 
let safeDiv10 y = if y = 0 then None else Some (10/y)
let optSafeDiv10 = Option.bind safeDiv10
optSafeDiv10 None       // None
optSafeDiv10 (Some 2)   // Some 5

Available functions are in the Option namespace, use Visual Studio to see what is available.

Lists level 2

Most of the functions in the List module are also available in the Seq or Array module. Some of the List functions and their LINQ counterpart

F# C#
List.filter .Where
List.map .Select
List.mapi .Select (with the index as well)
List.fold .Aggregate
List.find .First
List.tryFind .FirstOrDefault
List.collect .SelectMany
List.exist .Any

List functions examples

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let numbers = [ 1 .. 10 ]
let evenNumbers = numbers |> List.filter (fun x -> x % 2 = 0)
let sum = numbers |> List.fold (fun x y -> x + y) 0
let double = numbers |> List.map ((*) 2)
let doubleSum = 
    numbers
    |> List.map ((*) 2)
    |> List.fold (+) 0

Check the List module for more functions.

Exercises - Module 3

Goal

  • Implement the domain logic for our ticketing system

Exercise 3.1 - Conversion functions

  • Comment out one test at a time from the ConversionTests module in FunctionsTests.fs
  • Add a F# source file named Functions.fs in the FSharpIntro project below Types.fs (right click Types.fs and choose Add below -> New Item...)
  • Add open System and open Types belove the top module definition
  • Add a sub module Conversion in the new file
1: 
module Conversion =
  • Add the implementation that passes the test to this module, note that the functions need to be indented under this module
  • (Hint on next page)

Exercise 3.1 - Hint

Implementation of the first function:

1: 
let priceToPoints (price: float<SEK>)= price * 1.<Point/SEK>

Exercise 3.2 - Customer functions

  • Copy the file Lists.fs and add it over Types.fs
  • Add a new module in the Functions.fs file named Customer
1: 
module Customer =
  • Comment out one test at a time and make it pass
  • When implementing calculatePoints you can use the takeXOrAll function in Lists.fs

Exercise 3.3 - Ticket functions

  • Add a new module in the Functions.fs file named `Ticket
1: 
module Ticket = 
  • Comment out one test at a time and make it pass

Module 4

Maps and basic OO

Maps

A Map can be thought of as a immutable Dictionary

Maps - key functions

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
// Map.ofList
[1,2; 2,1] |> Map.ofList // map [(1, 2); (2, 1)]

// Map.toList
[1,2; 2,1] |> Map.ofList |> Map.toList // [(1, 2); (2, 1)]

// Map.add
[1,2; 2,1] |> Map.ofList |> Map.add 3 4 // map [(1, 2); (2, 1); (3, 4)]

// Map.find
[1,2; 2,1] |> Map.ofList |> Map.find 1 // 2

// Map.tryFind
[1,2; 2,1] |> Map.ofList |> Map.tryFind 1 // Some 2

Use Map.find if you know that the value exists, otherwise use tryFind and deal with the missing key.

Mutability in F#

  • In F# you want to avoid mutability, but some times it makes sense to bypass the default rules
  • You can create mutable variables by using the keyword mutable and the <- operator to "re-bind" a variables.
  • Try to do it at the edges or keep it local

Mutability examples

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let mutable x = 5
x <- 6
x = 5 // false
x = 6 // true

let mutable list = [1; 2]
list <- 3::list
list = [1; 2]       // false
list = [3; 1; 2]    // true

Creating classes

The syntax is similar to records but you take arguments after the type name.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
type Person(name, age) = 
    let isAdult = age > 18
    do printfn "%s is adult: %A" name isAdult
    member this.Name = name // get & private set of Name
    member this.Age = age   // get & private set of age
    member this.IsAdult = isAdult

let tomas = Person("tomas", 35)
let tomas = new Person("tomas", 35)

Interfaces

An interface is a type with just abstract members.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
type Adder = 
    abstract member Add: int -> int -> int

type AdderImpl() = 
    interface Adder with
        member this.Add x y = x + y

let adder = AdderImpl()
adder.Add 3 4 // ERROR
(adder :> Adder).Add 3 4 // 7 -- (:>) is a type safe cast

Object expressions

It is possible to implement an interface in F# without implementing a class. It is done by a so called Object expression.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
type Adder = 
    abstract member Add: int -> int -> int

let adder = 
    { new Adder
        with member this.Add x y = x + y }

adder.Add 3 4 // 7, no need to cast since adder is of type Adder

Exercises - Module 4

Goal

  • Implement in-memory data storage
  • Learn more about lists, maps, OO and mutability

Exercise 4.1 - Implement the in-memory store for destinations

  • Add the file Data.fs below the Functions.fs file
  • Comment out one test a time and make it pass
  • Key functions you might want to use
    • List.mapi
    • List.collect
    • Map.ofList
  • There are hints to get you started on the next two slides

Exercise 4.1 - Hints

Some helper values:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let private destinationList = 
    [ "Barcelona"; "Stockholm"; "Oslo" ] 
    |> List.mapi Destination.create
let private distanceList = 
    [ (0,1,2789.2); (0,2,2141.16); (1,2,530.) ]
    |> List.collect (fun (index1, index2, distance) -> 
        let typedDistance = distance |> createDistance
        [
            (destinationList.[index1], destinationList.[index2]), typedDistance
            (destinationList.[index2], destinationList.[index1]), typedDistance
        ])
    |> Map.ofList

Exercise 4.1 - More hints

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
let private priceList =
    [
        (0,1,700., 1500., 2000.)
        (0, 2, 600., 1300., 1800.)
        (1, 2, 400., 700., 1000. ) ]
    |> List.collect
        (fun (index1, index2, economyPrice, businessPrice, firstClassPrice) ->
            let [
                    typedEconomyPrice
                    typedBusinessPrice
                    typedFirstClassPrice ] =
                [economyPrice; businessPrice; firstClassPrice]
                |> List.map createPrice
            let prices = 
                (typedEconomyPrice, typedBusinessPrice, typedFirstClassPrice
            [
                (destinationList.[index1], destinationList.[index2]), prices
                (destinationList.[index2], destinationList.[index1]), prices
            ])
    |> Map.ofList

Exercise 4.2 - Implement the in-memory store for tickets

  • Comment out the TicketsTests and implement the functions it covers
  • (This is probably not the best example of a good unit test)

Exercise 4.3 - Implement the in-memory store for Customers

  • Comment out the tests in CustomerTests class one at a time and make it pass
  • Note that we are using a class here so we can initialize the tests in the constructor block. That is the code after do.

Exercise 4.4 - Implement DestinationService

  • Create a new file below Data.fs and name it Services.fs
  • Add the class DestinationService to the file
1: 
type DestinationService() = 
  • Add the following member functions that just call the functions in the Data module:
1: 
2: 
3: 
4: 
member this.GetDestinations = // add correct function call here
member this.GetDestination destinationId = // , here
member this GetDistance ((destination1, destination2) as key) = // , here
member this.GetPrice ((destination1, destination2) as key, ticketClass) // and here

Exercise 4.5 - Implement TicketService

  • Add the class TicketService
  • Add the members functions GetTicketsForCustomer and SaveTicket

Exercise 4.6 - Implement CustomerService

  • Add the class CustomerService
  • Add the member functions GetCustomer, GetCustomers, AddCustomer and UpdateCustomer

Exercise 4.7 - Experiment with the services

This time we will test that the code work through the FSI. Open up the file Script1.fsx and paste in the following to get you started:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
#load "Lists.fs"
#load "Types.fs"
#load "Functions.fs"
#load "Data.fs"
#load "Services.fs"

open Services

let customerService = new CustomerService()
customerService.GetCustomers()
customerService.AddCustomer "Tomas"
customerService.GetCustomer 0

Run with Alt+Enter in Visual Studio.

Module 5

Composition

Composing applications

When composing components it is done in mostly two different places:

  • Compose types
  • Compose functions

Composing types

Can be used to define external interface from an application

1: 
2: 
3: 
4: 
5: 
6: 
7: 
type App =
    {
        getCustomers: unit -> Customer list
        addCustomer: CustomerName -> Customer option
        reserveTicket: ReserveTicketRequest -> (Customer * Ticket) option
        getTickets: CustomerId -> Ticket list
    }

Composing functions

Sometimes the function you want to expose does not match in type and/or structure with the domain function, or you need to combine multiple functions together.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let createTicket request customer = 
    let ticket = Domain.createTicket customer request // Wrapping domain function
    match ticket with
    | None -> None
    | Some ticket -> Some (customer, ticket) // Creating new return value

let reserveTicket ticketRequest : (Customer, Ticket) option =
    getCustomer customerId  // returns Customer option
    Option.bind (createTicket ticketRequest) // Return option
    ...

let app = {
    reserveTicket: reserveTicket
}

Exercises - Module 5

Goal

  • Gluing the pieces together

Exercise 5.1 - Create Application types

  • Add an Application.fs file below Services.fs
  • Create a record type ReserveTicketRequest with the properties
    • CustomerId: CustomerId
    • From: DestinationId
    • To: DestinationId
    • TicketClass: TicketClass
  • Create another record type App with the properties
    • getCustomers: unit -> Customer list
    • addCustomer: CustomerName -> Customer option
    • reserveTicket: ReserveTicketRequest -> (Customer * Ticket) option
    • getTickets: CustomerId -> Ticket list

Exercise 5.2 - Add function to create the application

  • Add a createApp function in the bottom of the file (and keep it there always)
  • createApp should have three arguments
    • customerService: CustomerService
    • ticketService: TicketService
    • destinationService: TestinationService

Exercise 5.2 (cont.)

  • createApp should return a App
1: 
2: 
3: 
4: 
5: 
6: 
7: 
{
    getCustomers = customerService.GetCustomers
    addCustomer = customerService.AddCustomer
    reserveTicket = 
        (reserveTicket customerService ticketService destinationService)
    getTickets = ticketService.GetTicketsForCustomer
}
  • Implement a dummy function for reserveTicket
  • Try out the createApp with the script file

Exercise 5.3 - Implement reserveTicket

  • Add the reserveTicket function above createApp
1: 
2: 
3: 
4: 
let reserveTicket 
    (customerService: CustomerService) 
    (ticketService: TicketService) 
    (destinationService: DestinationService) reserveTicketRequest = 
  • Implement the following functions in the body of reserveTicket, some functions might shadow functions in Functions.fs:
1: 
2: 
3: 
4: 
5: 
6: 
7: 
let tryUpgradeTicketForCustomer (customer, ticket) 
    : (Customer * Ticket) option =

let createTicket 
    reserveTicketRequest
    ((customer: Customer), fromDestination, toDestination) 
    : (Customer * Ticket) option = 

Exercise 5.3 - Implement reserveTicket (cont.)

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let getFromDestionation reserveTicketRequest customer 
    : (Customer * Destination) option = 

let getToDestination reserveTicketRequest (customer, from) 
    : (Customer * Destination * Destination) option = 

let updateCustomer ((customer:Customer), ticket)
    : (Customer * Ticket) option = 

let saveTicket (customer, ticket) 
    : (Customer * Ticket) option = 

let saveCustomer (customer, ticket) 
    : (Customer * Ticket) option = 

Exercise 5.4 - Bind the helper functions together

  • Use Option.bind and do the following:

    1. get customer
    2. get from destination
    3. get to destination
    4. create ticket
    5. try upgrade ticket for customer
    6. save ticket
    7. update customer
    8. save customer
  • Hint on next slide to get you started

Exercise 5.4 - Hint

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let reserveTicket ... = 

    // ... helper functions

    customerService.GetCustomer reserveTicketRequest.CustomerId
    Option.bind (getFromDestionation reserveTicketRequest)
    // add the rest here

Exercise 5.5 - Create the console application

  • This is mainly boiler plate code so replace Program.fs with the one here.
  • This is the first function with recursion, the rec keyword is used
  • Try to understand the code (reading code is important)
  • Start the application and play around with it

The End

  • For two extra exercises see the branches wep-api and typeprovider