type Person =
{FirstName: string;
LastName: string;
Age: int;}
Full name: index.Person
Person.FirstName: string
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
Person.LastName: string
Person.Age: int
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<_>
val p : Person
Full name: index.p
val point : float * float
Full name: index.point
val personWithAge : string * int
Full name: index.personWithAge
val x : float
Full name: index.x
val y : float
Full name: index.y
type MyTuple = string * int * Person
Full name: index.MyTuple
type LoggingLevel =
| Debug
| Info
| Error
Full name: index.LoggingLevel
union case LoggingLevel.Debug: LoggingLevel
union case LoggingLevel.Info: LoggingLevel
union case LoggingLevel.Error: LoggingLevel
val level : LoggingLevel
Full name: index.level
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
Full name: index.Shape
union case Shape.Circle: radius: float -> Shape
Multiple items
val float : value:'T -> float (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.float
--------------------
type float = System.Double
Full name: Microsoft.FSharp.Core.float
--------------------
type float<'Measure> = float
Full name: Microsoft.FSharp.Core.float<_>
union case Shape.Rectangle: width: float * height: float -> Shape
val circ : Shape
Full name: index.circ
val rect : Shape
Full name: index.rect
val calculateArea : shape:Shape -> float
Full name: index.calculateArea
val shape : Shape
val w : float
val h : float
val r : float
val circleArea : float
Full name: index.circleArea
type OperationResult =
| Success of data: obj
| NotFound
| Error of errorMessage: string
Full name: index.OperationResult
union case OperationResult.Success: data: obj -> OperationResult
namespace Microsoft.FSharp.Data
union case OperationResult.NotFound: OperationResult
union case OperationResult.Error: errorMessage: string -> OperationResult
type HttpRequest =
| Get of url: string
| Post of url: string * body: byte array
Full name: index.HttpRequest
union case HttpRequest.Get: url: string -> HttpRequest
union case HttpRequest.Post: url: string * body: byte array -> HttpRequest
Multiple items
val byte : value:'T -> byte (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.byte
--------------------
type byte = System.Byte
Full name: Microsoft.FSharp.Core.byte
type 'T array = 'T []
Full name: Microsoft.FSharp.Core.array<_>
type Credentials =
| UserPass of user: string * password: string
| Certificate of certFilePath: string
| None
Full name: index.Credentials
union case Credentials.UserPass: user: string * password: string -> Credentials
union case Credentials.Certificate: certFilePath: string -> Credentials
union case Credentials.None: Credentials
Introduction to
functional data types in F#
Mark Vincze - @mrkvincze
The plan
- Sample problem
- Implement in C#
- Basics of F# data types
- Implement in F#
The exercise
- Implement a data model for the standard 52-card deck (French playing cards)
- 4 suits: ♦ ♥ ♣ ♠
- 9 value cards from 2 to 10
- 4 face cards: Jack, Queen, King, Ace
- Special card without a suit: Joker
Additional requirements
Implement a standalone data model, not tied to any algorithm
-
Adhere to Domain Driven Design
Main principles
- Use terminology natural to domain experts
- Make illegal states not representable
"Business" logic example:
Rummy scoring
-
Calculate the score of a card in Rummy
- Queen of spades: 40
- Aces: 15
- Other face cards: 10
- 10s: 10
- Everything else: 5
Data models in OO languages
- Immutability is not the default
- "Either" is not easy to express
- Inheritance and pure data do not play well
Data types in FP languages
- Immutability by default
- Composability
- Value semantics
- Originates from set theory and category theory
Record
- Immutable reference type objects
1:
2:
3:
4:
5:
|
type Person = {
FirstName : string
LastName : string
Age : int
}
|
Record
1:
2:
3:
4:
5:
6:
7:
8:
9:
|
type Person = {
FirstName : string
LastName : string
Age : int
}
let p = { FirstName = "Jane"; LastName = "Smith"; Age = 25 }
p.Age <- 30 // This doesn't compile
|
Record
1:
2:
3:
4:
5:
6:
7:
8:
9:
|
type Person = {
FirstName : string
LastName : string
mutable Age : int
}
let p = { FirstName = "Jane"; LastName = "Smith"; Age = 25 }
p.Age <- 30 // Now this works
|
Tuple
- Multiple values put together
1:
2:
3:
4:
5:
6:
|
let point = (1.5, 4.3)
let personWithAge = ("Jane Smith", 25)
let x, y = point
type MyTuple = string * int * Person
|
Tuple
Also called: Product type
*
=
A*B
a1, b1 |
a2, b1 |
a3, b1 |
a1, b2 |
a2, b2 |
a3, b2 |
Discriminated union
-
Represents a choice of multiple options
Example without data
1:
2:
3:
4:
5:
6:
|
type LoggingLevel =
| Debug
| Info
| Error
let level = Info
|
Discriminated union
-
Every case can have a value
Example
1:
2:
3:
4:
5:
6:
|
type Shape =
| Circle of radius : float
| Rectangle of width : float * height : float
let circ = Circle (2.0)
let rect = Rectangle (width = 10.0, height = 2.3)
|
Discriminated union
1:
2:
3:
4:
5:
6:
|
let calculateArea shape =
match shape with
| Rectangle (w, h) -> w * h
| Circle r -> r * r * 3.14
let circleArea = calculateArea (Circle 5)
|
Discriminated union
Also called: Sum type
Discriminated unions in practice
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
|
type OperationResult =
| Success of data : Data
| NotFound
| Error of errorMessage : string
type HttpRequest =
| Get of url : string
| Post of url : string * body : byte array
type Credentials =
| UserPass of user : string * password : string
| Certificate of certFilePath : string
| None
|