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#
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:
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
|
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
|
1:
2:
|
type MyString = string
type MyInt = int
|
Types 2
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
|
1:
2:
|
let x = ("tomas", 35)
let (name, age) = x
|
Types 3
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
|
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
|
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
1:
2:
|
let add x y = x + y
let add5 = add 5
|
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.
1:
2:
3:
|
(|>) // ('a -> ('a -> 'b) -> 'b)
let (|>>>) = fun x f -> f x
|
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|]
|
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
|
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.
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
- 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
- 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
- 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
|
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.
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
}
|
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
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